Sonntag, 31. August 2014

Define polymorphic ActiveRecord model association!

If you feel yourself in the position that one of your ActiveRecord models belongs to more than one other model, you are facing a polymorphic association.
Polymorphism is the provision of a single interface to entities of different types.
A polymorphic association saves you from:
  1. multiple alike tables and models (and therefore logic repetition)
  2. extensive meaningless foreign key attributes
  3. abuse of STI pattern
The original codes reveals a simple association between Comment and Article:
class Comment < ActiveRecord::Base
  belongs_to :article
end

class Article < ActiveRecord::Base
  has_many :comments
end
If also the Author should be commentable, adding a second foreign key attribute author_id is not a good choice (one of the foreign key attribute is meaningless in every case and it is horrible to validate both associations not interfering each other).
Adding a second model e.g. AuthorComment also is no option for duplication reasons.
On the other hand converting the association to a polymorphic association is fairly easy:
1.) rename the foreign key attribute article_id to the more abstract commentable_id and migrate it:
rename_column :comments, :article_id, :commentable_id
2.) add a new attribute commentable_type and migrate it:
add_column :comments, :commentable_type, :string
The attribute commentable_type is for defining the name of the associated class like Article and Author as string. It should always end with '_type'. Otherwise you have to configure it by the option :foreign_type on the belongs_to association.
3.) the model is ready for the polymorphic association:
class Comment < ActiveRecord::Base
  belongs_to :commentable,
    polymorphic: true
end
4.) multiple models can use it:
class Article < ActiveRecord::Base
  has_many :comments,
    as: :commentable
end

class Author < ActiveRecord::Base
  has_many :comments,
    as: :commentable
end
The models are based on the ERM:

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17