I constantly find myself reviewing and questioning my domain model. Recently I noticed a pattern in 2 different webapps.
Here’s the requirement from the client
The web site has groups. Each group has a forum. Users can create and comment on posts in the forum.
A quick scan of that sentence finds 5 nouns we care about
Lets forget about modeling users for now and concentrate on the other 4.
class Group < ActiveRecord::Base has_one :forum def before_create self.forum = Forum.new end end class Forum < ActiveRecord::Base belongs_to :group class << self def popular end def recent end end end class Post < ActiveRecord::Base belongs_to :forum has_many :comments end class Comment < ActiveRecord::Base belongs_to :post end
groups (id, name) forums (id, group_id) posts (id, title, body, forum_id) comments (id, body, post_id)
Thats a real simple schema (im still ignoring users for the time being as well so no user_id in any table yet).
Looking at the requirement, finding the nouns and then creating classes for each noun is a really simple modeling technique. In this instance it yielded 4 classes (actually 5 but were ignoring users for now).
Now if an object has state usually I want to record this state in a table in the
db. Looking at the schema for that
forums table shows nothing but db keys, no
state at all. I don’t want tables in my db that are unnecessary so lets try to
get rid of it.
Looking back at our
Forum model we see that it does have some useful behavior
class Forum < ActiveRecord::Base belongs_to :group class << self def popular end def recent end end end
It has 2 class side finders
#recent. I want to keep those. In
ruby when we have behavior and no state we don’t use classes, we use modules.
A forum is a discussion so we’ll name our module along those lines
module Discussable def self.included(clazz) clazz.has_many :posts clazz.extend ClassMethods end module ClassMethods def popular end def recent end end end
Now let’s mix this into our
Group model and look at the domain model now
class Group < ActiveRecord::Base include Discussable end class Post < ActiveRecord::Base belongs_to :group has_many :comments end class Comment < ActiveRecord::Base belongs_to :post end
We’ve eliminated the
Forum class and made a
instead. The interesting behavior that was in our old
Forum class now resides
Discussable module which is then mixed into
Let’s look at some usage now.
Before we’d go
Now we do
That’s going to be a problem once groups can be considered popular and recent by
means other than the activity in their forums. That kind of makes me question
Forum class. I guess when that problem arises we could rename
#recent_forums. In Ruby it is more
accurate to model behavior only classes using modules. Once you need state with
your behavior then its time to use a class.
Forum are what I like to call container/collection classes.
These classes have no state and are used solely for managing a collection of
other objects, in the example above that would be a forum and its posts. They
usually arise from over-modeling but sometimes they contain useful behavior.
Question these classes and see if you can eliminate them.
After some ask.com'ing i found some database design articles saying one to one associations were indications of a poor design.