Rails self referential has_many through
My first ruby on rails project is to redevelop the database, backend and frontend of an existing product. It is an information resource for staff wellbeing used by universities, nhs trusts and large companies. The resource consists of about 300 articles which are linked in a diagnostic "How are you today?" tree. This means a lot of the value of the application is in the links between the articles.
So my first task was to learn how to do self referential links between articles in ruby on rails.
To demonstrate how it works here's a small demo app, just of articles and links. Here's the schema - articles have a name and body; links have parent_id and child_id to make the links between articles and weight to enable sorting.
[rb title="db/schema.rb"]ActiveRecord::Schema.define(:version => 20120115171005) do
create_table "articles", :force => true do |t|
t.string "name"
t.string "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "links", :force => true do |t|
t.integer "parent_id"
t.integer "child_id"
t.integer "weight"
t.datetime "created_at"
t.datetime "updated_at"
end
end
[/rb]
Here are the article and link models:
[rb title="app/models/article.rb"]
class Article < ActiveRecord::Base
has_many :child_links, :class_name => "Link", :foreign_key => :parent_id
has_many :children, :through => :child_links, :source => :child
has_many :parent_links, :class_name => "Link", :foreign_key => :child_id
has_many :parents, :through => :parent_links, :source => :parent
end
[/rb]
[rb title="app/models/link.rb"]
class Link < ActiveRecord::Base
belongs_to :parent, :class_name => "Article"
belongs_to :child, :class_name => "Article"
end
[/rb]
How does it work?
You make the links belong_to parent and child - both of class_name "Article". This means you can refer to articles as parents or children.
Then in the article model, you make sure you set up the foreign key for the has_many, and the source for the has_many through the right way round:
[rb]
has_many :child_links, :class_name => "Link", :foreign_key => :parent_id
has_many :children, :through => :child_links, :source => :child
[/rb]
parent_id is the foreign_key because child_links links from a parent to a child, and the article creating the link is the parent. Then the children come through this link (which is turned into the singular in the link model - rails is clever with all this pluralization stuff) as the source.
And visa versa with parent_links and parents.
Then in the rails console, if you create 2 articles id 1 & 2, you can do
[rb]
Article.find(1).children
[/rb]
and get Article 2.
[rb]
Article.find(2).parents
[/rb]
to get Article 1.
Very useful once you get your head round it!
I've put a small demo app on github here.