4.11. Polymorphic Associations

Already the word "polymorphic" will probably make you tense up. What can it mean? Here is what the website http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html tells us: Polymorphic associations on models are not restricted on what types of models they can be associated with. Well, there you go - as clear as mud! ;-)
I am showing you an example in which we create a model for cars (Car) and a model for bicycles (Bike). To describe a car or bike, we use a model to tag it (Tag). A car and a bike can have any number of tags. The application:
$ rails new example
  [...]
$ cd example 
$
Now the three required models:
$ rails generate model car name
  [...]
$ rails generate model bike name
  [...]
$ rails generate model tag name taggable_type taggable_id:integer
  [...]
$ rake db:migrate
  [...]
$
Car and Bike are clear. For Tag we use the fields taggable_type and taggable_id, to give ActiveRecord an opportunity to save the assignment for the polymorphic association. We have to enter it accordingly in the model.
app/models/tag.rb
class Tag < ActiveRecord::Base
  attr_accessible :name, :taggable_id, :taggable_type

  belongs_to :taggable, :polymorphic => true
end
app/models/car.rb
class Car < ActiveRecord::Base
  attr_accessible :name

  has_many :tags, :as => :taggable  
end
app/models/bike.rb
class Bike < ActiveRecord::Base
  attr_accessible :name

  has_many :tags, :as => :taggable
end
For Car and Bike we use an additional :as => :taggable when defining has_many. For Tag we use belongs_to :taggable, :polymorphic => true to indicate the polymorphic association to ActiveRecord.

Tip

The suffix able in the name taggable is commonly used in Rails, but not obligatory. For creating the association we now not only need the ID of the entry, but also need to know which model it actually is. So the term taggable_type makes sense.
Let's go into the console and create a car and a bike:
$ rails console
Loading development environment (Rails 3.2.9)
>> beetle = Car.create(name: 'Beetle')
   (0.1ms)  begin transaction
  SQL (24.5ms)  INSERT INTO "cars" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 15:02:35 UTC +00:00], ["name", "Beetle"], ["updated_at", Sun, 18 Nov 2012 15:02:35 UTC +00:00]]
   (2.2ms)  commit transaction
=> #<Car id: 1, name: "Beetle", created_at: "2012-11-18 15:02:35", updated_at: "2012-11-18 15:02:35">
>> mountainbike = Bike.create(name: 'Mountainbike')
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "bikes" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 15:03:10 UTC +00:00], ["name", "Mountainbike"], ["updated_at", Sun, 18 Nov 2012 15:03:10 UTC +00:00]]
   (2.0ms)  commit transaction
=> #<Bike id: 1, name: "Mountainbike", created_at: "2012-11-18 15:03:10", updated_at: "2012-11-18 15:03:10">
>> 
Now we define for each a tag with the color of the corresponding object:
>> beetle.tags.create(name: 'blue')
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Sun, 18 Nov 2012 15:03:45 UTC +00:00], ["name", "blue"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Sun, 18 Nov 2012 15:03:45 UTC +00:00]]
   (0.7ms)  commit transaction
=> #<Tag id: 1, name: "blue", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:03:45", updated_at: "2012-11-18 15:03:45">
>> mountainbike.tags.create(name: 'black')
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Sun, 18 Nov 2012 15:04:01 UTC +00:00], ["name", "black"], ["taggable_id", 1], ["taggable_type", "Bike"], ["updated_at", Sun, 18 Nov 2012 15:04:01 UTC +00:00]]
   (3.0ms)  commit transaction
=> #<Tag id: 2, name: "black", taggable_type: "Bike", taggable_id: 1, created_at: "2012-11-18 15:04:01", updated_at: "2012-11-18 15:04:01">
>>
For the beetle, we add another Tag:
>> beetle.tags.create(name: 'Automatic')
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Sun, 18 Nov 2012 15:05:17 UTC +00:00], ["name", "Automatic"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Sun, 18 Nov 2012 15:05:17 UTC +00:00]]
   (3.0ms)  commit transaction
=> #<Tag id: 3, name: "Automatic", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:05:17", updated_at: "2012-11-18 15:05:17">
>>  
Let's have a look at all Tag items:
>> Tag.all
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" 
=> [#<Tag id: 1, name: "blue", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:03:45", updated_at: "2012-11-18 15:03:45">, #<Tag id: 2, name: "black", taggable_type: "Bike", taggable_id: 1, created_at: "2012-11-18 15:04:01", updated_at: "2012-11-18 15:04:01">, #<Tag id: 3, name: "Automatic", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:05:17", updated_at: "2012-11-18 15:05:17">]
>> 
And now all tags of the beetle:
>> beetle.tags
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" WHERE "tags"."taggable_id" = 1 AND "tags"."taggable_type" = 'Car'
=> [#<Tag id: 1, name: "blue", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:03:45", updated_at: "2012-11-18 15:03:45">, #<Tag id: 3, name: "Automatic", taggable_type: "Car", taggable_id: 1, created_at: "2012-11-18 15:05:17", updated_at: "2012-11-18 15:05:17">]
>>
Of course you can also check which object the last Tag belongs to:
>> Tag.last.taggable
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" ORDER BY "tags"."id" DESC LIMIT 1
  Car Load (0.2ms)  SELECT "cars".* FROM "cars" WHERE "cars"."id" = 1 LIMIT 1
=> #<Car id: 1, name: "Beetle", created_at: "2012-11-18 15:02:35", updated_at: "2012-11-18 15:02:35">
>> exit
$
Polymorphic associations are always useful if you want to normalize the database structure. In this example, we could also have defined a model CarTag and BikeTag, but as Tag is the same for both, a polymorphic association makes more sense in this case.

Note

Polymorphic associations are very useful. But you should always bear in mind that they cause a greater load on the database than a normal 1:n association. Normally this would not make any difference, but you should keep it in mind when planning.

Updates about this book will be published on my Twitter feed.