HOW TO create Rails 3 Polymorphic has_many :through Relationships

Feb 8, 2012 by Tilo Sloboda. Written in Rails 3.2

 

Here is a short working example on how you can create polymorphic has_many :through relationships in Rails 3.

Imagine you have an Author model. Each Author can write either Books or Articles (or other things). Each author can have a multitude of publications, and each publication can have multiple authors.

We want to be flexible on the kind and number of publications we model, and want that side of the many-to-many relationship to be polymorphic. Clearly we will need a join model between the publications and the authors. We want to store more information in that join model, e.g. the publication year for now. We will call the join model Authorships, and use has_many :through to reach through to the other model on each side of the join table.

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
class Authorship < ActiveRecord::Base
  belongs_to :author
  belongs_to :publication , :polymorphic => true
end

class Author < ActiveRecord::Base
  has_many :authorships
  has_many :books, :through => :authorships, :source => :publication, :source_type => "Book"
  has_many :articles, :through => :authorships, :source => :publication, :source_type => "Article"
end

class Book < ActiveRecord::Base
  has_many :authorships, :as => :publication
  has_many :authors, :through => :authorships
end

class Article < ActiveRecord::Base
  has_many :authorships, :as => :publication
  has_many :authors, :through => :authorships
end

This allows you to easily access the other side of each relationship like this:

   a = Author.first
   a.books
   a.articles

   b = Book.first
   b.authors

   a = Article.first
   a.authors

Having set up the relations like this, allows us to easily extend this to have authors write other things, e.g. blogposts or comments. All we need to do is to create classes for those models similar to "Books" and "Articles" in the above example, and to extend the relations of "Author" to point to the blogposts or comments.

The Schema for this example looks like this:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
  ActiveRecord::Schema.define(:version => 20120208184823) do

  create_table "articles", :force => true do |t|
    t.string "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  create_table "authors", :force => true do |t|
    t.string "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  create_table "authorships", :force => true do |t|
    t.integer "author_id"
    t.integer "publication_id"
    t.string "publication_type"
    t.date "year"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  create_table "books", :force => true do |t|
    t.string "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

end

See also:

Gist for Polymorphic Rails 3 has_many :through Relationship