Monday 24 September 2012

Solution : Denormalize into Text Fields

Solution : Denormalize into Text Fields

Take a look at the following Article model and the associated State and Category models:

class Article < ActiveRecord::Base

  belongs_to :state
  belongs_to :category

  validates :state_id,         :presence => true

  validates :category_id,  :presence => true
end

class State < ActiveRecord::Base

  has_many :articles
end

class Category < ActiveRecord::Base

  has_many :articles
end

Given these models, the specific set of available states and categories would be loaded into the production application's respective database tables, and code for working with these associations would be as follows:


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


Of course, repeating the finder and the string for the published state is bad practice, so it might be wise to abstract that out into a custom finder on the State model:


@article.state == State.published


The code for dynamically defining these custom finder methods might be as follows:


class State < ActiveRecord::Base

  validates :name => :presence => true
  class << self
    all.each do |state|
      define_method "#{state}" do
        first(:conditions => { :name => state })
      end 
    end
  end
end

There is quite a bit of functionality associated with these types of models (states, categories, and so on) and, therefore, it's not desirable to allow end users or even administrators - to add or remove available states in the database. 


For Example, if the article publication workflow changes and a new state needs to be added, it's unlikely that an administrator can simply add the state to the database and have everything as desired.


You would denormalize the data from the state and category tables into the article table itself, and you would remove the State and Category models. 


When you do all this, the Article model looks as follows:


class Article < ActiveRecord::Base

  STATES = %W(draft review published archived)
  CATEGORIES = %w(tips faqs misc)

  validates :state,         :inclusion => {:in => STATES}

  validates :category,  :inclusion => {:in => CATEGORIES}

  STATES.each do |state|

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

  CATEGORIES.each do |category|

    define_method "#{category}?" do
      self.category == category
    end
  end

  class << self

    STATES.each do |state|
      define_method "#{state}" do
        state
      end
    end
   
    CATEGORIES.each do |category|
       define_method "#{category}" do
          category
       end
    end

  end


end


As you can see, the total code shown here for the normalized version is very similar to the code for the denomalized version. The dynamic methods are still being defined but the difference here is that the Article model now has state and category columns that contain a string representing the state instead of foreign key columns to hold the ID of the State and Category.


No comments:

Post a Comment