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:references{polymorphic}
  [...]
$ rake db:migrate
  [...]
$
Car and Bike are clear. For Tag we use the migration shortcut taggable:references{polymorphic} to generate 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.
The model generator already filed the app/models/tag.rb file with the configuration for the polymorphic association:
app/models/tag.rb
class Tag < ActiveRecord::Base
  belongs_to :taggable, polymorphic: true
end
For the other models we have to add the polymorphic association manually:
app/models/car.rb
class Car < ActiveRecord::Base
  has_many :tags, as: :taggable
end
app/models/bike.rb
class Bike < ActiveRecord::Base
  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.

Buy the new Rails 5.1 version of this book.

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 4.0.0)
>> beetle = Car.create(name: 'Beetle')
   (0.1ms)  begin transaction
  SQL (2.2ms)  INSERT INTO "cars" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Tue, 16 Jul 2013 13:04:50 UTC +00:00], ["name", "Beetle"], ["updated_at", Tue, 16 Jul 2013 13:04:50 UTC +00:00]]
   (0.9ms)  commit transaction
=> #<Car id: 1, name: "Beetle", created_at: "2013-07-16 13:04:50", updated_at: "2013-07-16 13:04:50">
>> mountainbike = Bike.create(name: 'Mountainbike')
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "bikes" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Tue, 16 Jul 2013 13:04:57 UTC +00:00], ["name", "Mountainbike"], ["updated_at", Tue, 16 Jul 2013 13:04:57 UTC +00:00]]
   (2.5ms)  commit transaction
=> #<Bike id: 1, name: "Mountainbike", created_at: "2013-07-16 13:04:57", updated_at: "2013-07-16 13:04:57">
>>
Now we define for each a tag with the color of the corresponding object:
>> beetle.tags.create(name: 'blue')
   (0.0ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Tue, 16 Jul 2013 13:05:19 UTC +00:00], ["name", "blue"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Tue, 16 Jul 2013 13:05:19 UTC +00:00]]
   (3.0ms)  commit transaction
=> #<Tag id: 1, name: "blue", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:19", updated_at: "2013-07-16 13:05:19">
>> mountainbike.tags.create(name: 'black')
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Tue, 16 Jul 2013 13:05:27 UTC +00:00], ["name", "black"], ["taggable_id", 1], ["taggable_type", "Bike"], ["updated_at", Tue, 16 Jul 2013 13:05:27 UTC +00:00]]
   (2.3ms)  commit transaction
=> #<Tag id: 2, name: "black", taggable_id: 1, taggable_type: "Bike", created_at: "2013-07-16 13:05:27", updated_at: "2013-07-16 13:05:27">
>> 
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", Tue, 16 Jul 2013 13:05:56 UTC +00:00], ["name", "Automatic"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Tue, 16 Jul 2013 13:05:56 UTC +00:00]]
   (2.1ms)  commit transaction
=> #<Tag id: 3, name: "Automatic", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:56", updated_at: "2013-07-16 13:05:56">
>>
Let's have a look at all Tag items:
>> Tag.all
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags"
=> #<ActiveRecord::Relation [#<Tag id: 1, name: "blue", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:19", updated_at: "2013-07-16 13:05:19">, #<Tag id: 2, name: "black", taggable_id: 1, taggable_type: "Bike", created_at: "2013-07-16 13:05:27", updated_at: "2013-07-16 13:05:27">, #<Tag id: 3, name: "Automatic", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:56", updated_at: "2013-07-16 13:05:56">]>
>>
And now all tags of the beetle:
>> beetle.tags
  Tag Load (0.4ms)  SELECT "tags".* FROM "tags" WHERE "tags"."taggable_id" = ? AND "tags"."taggable_type" = ?  [["taggable_id", 1], ["taggable_type", "Car"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 1, name: "blue", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:19", updated_at: "2013-07-16 13:05:19">, #<Tag id: 3, name: "Automatic", taggable_id: 1, taggable_type: "Car", created_at: "2013-07-16 13:05:56", updated_at: "2013-07-16 13:05:56">]>
>>
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" = ? ORDER BY "cars"."id" ASC LIMIT 1  [["id", 1]]
=> #<Car id: 1, name: "Beetle", created_at: "2013-07-16 13:04:50", updated_at: "2013-07-16 13:04:50">
>> 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.

Options

Polymorphic associations can be configured with the same options as a normal Section 4.8, “has_many – 1:n Association” model.