Encapsulation With Named Scopes

21 December 2009

While working on Ignitious, I wrote some code that looked something like this:

REXML could not parse this XML/HTML: 
<pre name="code" class="ruby"> 
class Commit < ActiveRecord::Base
  has_many :files, :class_name => 'CommitFile'
  has_many :added_files, :class_name => 'CommitFile', :conditions => {:state => 'added'}
  has_many :removed_files, :class_name => 'CommitFile', :conditions => {:state => 'removed'}
  has_many :modified_files, :class_name => 'CommitFile', :conditions => {:state => 'modified'}
end
 
class CommitFile < ActiveRecord::Base
  belongs_to :commit
end
 
'Created new records like this:'
Commit.files.create(:name => 'foo', :state => 'added')
</pre>

Then I realized, using named scopes, I could move all state logic into the CommitFile class like so:

REXML could not parse this XML/HTML: 
<pre name="code" class="ruby"> 
class Commit < ActiveRecord::Base
  has_many :files, :class_name => 'CommitFile'
end
 
class CommitFile < ActiveRecord::Base
  belongs_to :commit
  named_scope :added, :conditions => {:state => 'added'}, :order => 'filename'
  named_scope :removed, :conditions => {:state => 'removed'}, :order => 'filename'
  named_scope :modified, :conditions => {:state => 'modified'}, :order => 'filename'
end
 
'Create new records like this:'
Commit.files.added.new(:name => 'foo')
</pre>

That’s it! The Commit class no longer cares about the implementation of the status logic. It’s a subtle change, but one that I think will help with future refactorings.