Any rails screencast will usually show a simple blog example to get you started. But often they skip the many-to-many relationship and jump to the less complex one-to-many implementation of comments and posts. I instead wanted to start with the seemingly more difficult many tags to many posts relationship.
Scaffold out the objects and join table
First create a new rails application using a MySQL backend
rails new blog -d mysql
Change to the directory of your new rails app
cd blog
Next we need to scaffold out the post and tag objects
rails generate scaffold Post title:string body:text
rails generate scaffold Tag description:string
But this is where it gets a little tricky. Rails won't build the many-to-many join table so you will need to generate a specific migration that will hold these primary key values
rails generate migration create_posts_tags_join
After you have this file created you need to alter it. You can find this file under db/migrate/. It Should be named something like 0000000_create_posts_tags_join.rb. It's important to note that you must put these in alpha order ( ie p comes before t )
1
2
3
4
5
6
7
8
9
10
11
12
13
class CreatePostsTagsJoin < ActiveRecord::Migration
def self.up
create_table 'posts_tags', :id => false do |t|
t.column 'post_id', :integer
t.column 'tag_id', :integer
end
end
def self.down
drop_table 'posts_tags'
end
end
</pre>
Next in the post model add the following. This can be found under app/models
has_and_belongs_to_many :tags
And in the tag model add the following
has_and_belongs_to_many :posts
And finally do rake db:create followed by rake db:migrate from the command line to get your database setup correctly.
Alter your views to reflect this relationship
To get a working view first edit your _form.html.erb to include a checkbox of tags. This can be found under app/views/posts/
Next in the controller you need to add a variable for each element using the form. You can find this class under app/controllers/
1
2
3
4
5
6
7
8
9
10
def edit
@tags = Tag.all
...
end
def new
@tags = Tag.all
...
end
</pre>
Next alter the show.html.erb so we can see each tag for a given post - found under app/views/posts
Next you need to add a line to your show controller action that returns only the tags for a given post - again the posts_controller
1
2
3
4
5
6
7
8
9
def show
@post = Post.find(params[:id])
@tags = @post.tags
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @post }
end
end
And one last tweak to fix an issue with a 'no many-to-many' update. Alter your update method to show the tag_ids array as null when it's empty. This will clear the collection from @post when you submit a form post without any tags selected.
1
2
3
4
5
def update
params[:post][:tag_ids] ||= []
@post = Post.find(params[:id])
...
end
Lets see this in action. Fire up the rails web server
rails server
Now after you have the above in place go to the url below and add a few tags so we have some to play with
http://localhost:3000/tags
Next go to the post url below and add a post, but you should see a checkbox at the bottom that allows you to mark a post with a specific tag
http://localhost:3000/tags
Check a few and click update. Next edit it and remove them all. You now have your first many-to-many relationship working in rails 3
If you want the source for the finished blog application you can download it here