Search code examples
ruby-on-railsrubyoopactiverecordencapsulation

What is the purpose of this example from the 'Rails antipatterns' book: class << self defining methods in an ActiveRecord class


I'm spending some time reading Rails Antipatterns again. On page 81, there is an example of denormalizing attributes into text fields on a model. I'll simplify the example; please assume state is a column in the article table:

class Article < ActiveRecord::Base
  STATES = %w(review published)
  validates :state, :inclusion => {:in => STATES}

  STATES.each do |state|
    define_method "#{state}?" do
      self.state == state
    end
  end

  class << self
    STATES.each do |state|
      define_method "#{state}" do
        state
      end
    end
  end
end

What I'm trying to wrap my head around is the methods defined in the class << self block. When you say class << self and define methods within that black, since self is the class Article, I'm defining class methods for Article, right? Article.review, Article.published. I just don't see what the purpose would be. If the purpose is to set some accessors, why wouldn't those be defined in the upper loop where the first "#{state}?" methods are defined.

Is there any way this is a typo? I looked for errata on the book and didn't find anything.


Solution

  • The example in the book is a refactoring from this antipattern:

    @article.state = State.find_by_name("published")
    

    to this:

    @article.state = State.published
    

    and then to this:

    @article.state = Article.published
    

    The last one being implicit and not shown in the book.

    As you've noticed, there appears to be little point to making a class method that just returns its own name, so the real point of the example is not that this is the optimal way to do things, but as the book states:

    The most important thing is the complexity and code that it not represented here.

    i.e. that the dynamically created methods used to be in the State class and this class was unnecessary and could be removed.

    However, there is a further advantage to doing it this way. The purpose of the defined methods is to create a set of constants for the DB column with a standardised interface. Initially, the names of the constants will be the same as the names of the accessor methods, but in future there may be a reason for them to change. In this case, other areas of the program which may be calling these accessor methods can be left as they are.

    Contrast this with the alternative of hardcoding the actual string value of the constant all over the place. In that case, knowledge of the database structure of Article leaks out and creates dependencies, so that changes to the Article class internals (DB structure) will ripple out to other areas with unpredictable effects. By using messages instead of the actual value of the constant, these dependencies are removed and any refactoring will be trivial if it needs to happen. You can easily redefine any of those dynamic methods to be something more complex without causing anything else outside of Article to need to change.

    In other words: in order to properly encapsulate data, good OO design requires that the knowledge of the names of the database columns should not leave the Article class. You should always prefer methods over variables in order to make this possible, which is what that block of class methods does.