The following blog post contains material either currently found or soon to be incorporated into my new book, "Easy Active Record for Rails Developers". Among many other topics, you'll learn about model generation, migrations, validations, associations, scopes, joins, includes, forms integration, nested forms, and model testing with RSpec and FactoryGirl. The book is now available, head over to this website's home page to learn more.

In addition to adding user registration and account management features to ArcadeNomad, I’d like to soon give users the ability to comment on games and locations. It would be fun to read nostalgic notes about their favorite 80’s video game and reviews of a recent visit to one of the locations. To implement such a feature, one might presume we need to create separate comment models in order to associate both games and locations with their respective comments, meaning we would separately manage two sets of comments. This approach would however be repetitive because each model would presumably consist of the same data structure. You can eliminate this repetition using a polymorphic association.

Let’s work through a simplified example that would use polymorphic associations to add anonymous commenting capabilities to the Game and Location models. Begin by creating a new model named Comment:

$ rails g model Comment body:text commentable_id:integer commentable_type:string

Next, open up the newly created Comment model (app/models/comment.rb) and edit it to look like this:

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true

By declaring the model association as polymorphic, the model will look to the commentable_type and commentable_id attributes for the associated model type and the associated model record’s primary key, respectively. Therefore if a comment were associated with a location having the record ID 112, then that record’s commentable_type field would be set to Location and the commentable_id field would be set to 112. However, before this will work we need to complete configuration the association by modifying the Game and Location models. Open up these two models and add the following line alongside your other associations:

has_many :comments, :as => :commentable

Finally, run the migration and reload your console. With these changes in place, you can begin taking advantage of the polymorphic association. Let’s try it out by attaching a comment to a game:

>> game = Game.find(1)
>> game.comments << Comment.create(body: "I love 1942!")
(0.2ms)  BEGIN
  SQL (0.5ms)  INSERT INTO `comments` (`body`, `created_at`, `updated_at`) 
  VALUES ('I love 1942!', '2014-08-04 16:30:33', '2014-08-04 16:30:33')
(0.8ms)  COMMIT
(0.3ms)  BEGIN
  SQL (0.7ms)  UPDATE `comments` SET `commentable_id` = 1,
   `commentable_type` = 'Game', `updated_at` = '2014-08-04 16:30:33'
   WHERE `comments`.`id` = 1
(0.6ms)  COMMIT
Comment Load (0.3ms)  SELECT `comments`.* FROM `comments`
  WHERE `comments`.`commentable_id` = 1 AND `comments`.`commentable_type` = 'Game'

Now let’s attach a comment to a location:

>> location = Location.find(1)
>> location.comments << Comment.create(body: "My favorite bar!")
(0.2ms)  BEGIN
  SQL (1.7ms)  INSERT INTO `comments` (`body`, `created_at`, `updated_at`)
  VALUES ('My favorite bar!', '2014-08-04 16:28:25', '2014-08-04 16:28:25')
(0.2ms)  COMMIT
(0.1ms)  BEGIN
  SQL (0.7ms)  UPDATE `comments` SET `commentable_id` = 1,
  `commentable_type` = 'Location', `updated_at` = '2014-08-04 16:28:25'
  WHERE `comments`.`id` = 2
 (0.2ms)  COMMIT

With these two comments added, review your comments table and you’ll see the following contents:

mysql> select * from comments;
| id | body             | commentable_id | commentable_type | created_at | updated_at |
|  1 | I love 1942!     |              1 | Game             | ...        | ...        |
|  1 | My favorite bar! |              1 | Location         | ...        | ...        |

As you can see, polymorphic associations are a powerful way to reduce the amount of redundancy you’d otherwise have to introduce into your application to integrate data-oriented features that touch multiple models!

Like what you read? There’s plenty more where this came from in my new book, “Easy Active Record for Rails Developers”!