for purpose of reference: doing many-to-many relationships in Rails
There are lots of tutorials out on Rails, and many people may need to work through them to get a grasp of Rails. but once you’re experienced in programming you might feel delayed by all the stuff the tutorial brings up but which you already know. Most of the tutorials I’ve seen so far also prevent you from skipping over to go straightly to the interesting parts because they do stuff in between that affects the interesting parts, so you need to go backwards through the tutorial to figure out what’s affect only and what actually needs to be there so that what you intend will work.
I find that rather annoying. I’d like to have a simple overview of what files need to get modified to get a many-to-many relationship between two tables. For starters, it’s rather useless to know all the details, gimmicks and features you might want to use once you’re a bit more experienced: If you come from a different development language, you probably don’t want anything as simple as a single-table application to get a first impression of Rails for yourself. I want to know how I can implement a many-to-many, and how to make that visible. Nothing more, nothing less.
For the example, I use tags and items: Each tag may be related to multiple items; each item may have multiple tags. (For example, the tags {sunset, sun, water} may be related to a sunset picture. Thus, there are multiple tags related to a single picture yet. And another picture might have the tags {tap, water, damp, dishes}. Thus ‘water’ would be a tag that’s related to more but a single item.)
We’ve got three scaffolds to do, one for the items, one for the tags and one for the many-to-many relationship. The three scaffolds create three model files. For simplicity, we start with a whole new project, so there won’t be any side effects of whatever project we might be working on already:
$ rails many_to_many && cd many_to_many
... [output] ...
Next, we do the scaffolds:
$ ./script/generate scaffold item name:string && rake db:migrate
... [output] ...
$ ./script/generate scaffold tag label:string && rake db:migrate
... [output] ...
$ ./script/generate scaffold item_tag item_id:integer tag_id:integer && rake db:migrate
... [output] ...
Once done with that, we have everything in place to set up the many-to-many relationship. We modify the models first, then the output for a single item to make it show the tags for that item also:
file: app/models/item.rb:
class Item < ActiveRecord::Base has_many :item_tags has_many :tags, :through => :item_tags end
(The bold code lines are those you need to insert into the model. Same for the two others below.)
file: app/models/tag.rb:
class Tag < ActiveRecord::Base has_many :item_tags has_many :items, :through => :item_tags end
file: app/models/item_tag.rb:
class ItemTag < ActiveRecord::Base belongs_to :item belongs_to :tag end
And now the output. To meet the needs of the Model View Controller pattern, to get the appropriate output, we need to modify the controller and view of the item(s). First we modify the controller, then the view; in the controller we modify the show() method:
file: app/controllers/items_controller.rb:
show method before:
def show
@item = Item.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render
ml => @item }
end
end
show method after:
def show
@item = Item.find(params[:id])
@tagline = @item.tags.collect { |t| t.label }.join(', ')
respond_to do |format|
format.html # show.html.erb
format.xml { render
ml => @item }
end
end
In the view, we simply add a paragraph that outputs the tagline gotten by the controller:
file: app/views/items/show.html.erb:
<p> <b>Tags:</b> <%=h @tagline %> </p>
That mostly is it.
Now you can launch the server by the common
$ ./script/server
and direct your browser to
You can access items, tags and relationships between both by going to their respective views, like
http://localhost:3000/items http://localhost:3000/tags http://localhost:3000/item_tags
There you can enter some dummy data (using the ‘new’ link); to see the effect, I’d start by defining some items, then some tags, then some item_tag relationships, then view the individual item I related some tags to.
Notes:
- If you want to do something serious with tags, you might want to use the acts_as_taggable or the acts_as_taggable_on_steroids Rails plug-in. — Update: Here’s my quick reference on how to apply the latter.
- Some background info on many-to-many relationships in Rails (which I used to come up with the step-by-step intro above): Many-to-many Dance-off of 2006 by Josh Susser; a brand-new post on the same matter of June 2008 by Prashant Raju and Andrew Cetinick; a Railscast of June 2007 on still the same matter and, for reference, the Rails wiki on :through associations, date may vary.
- Furthermore, not only items:tags are common many-to-many relationships — contact lists are too. Here are two contact list maintaining tools in Rails I found along the way when I was after grasping the many-to-many issue: acts_as_network and Fischy Friends. — Contact lists resemble a social network, though a rather low-level one.
[t]
Galileocomputing released a great openbook: Ruby on Rails 2[1].
I think this should be the first thing to read to get a overview what is possible without the overhead most tutorials call content.
[1] http://www.galileocomputing.de/openbook/ruby_on_rails/
Timo Zimmermann
June 19, 2008
I am new to rails and I found a few tutorials on the subject a bit confusing. This is concise and clear. Very helpful
Eric
June 25, 2008
Eric,
That is a very nice compliment. Thank you!
dagobart
June 25, 2008