are there any tutorials out there how to scaffolding a simple model that uses many-to-many relationships?
-
1Googling your question gave me this: http://jrhicks.net/Projects/rails/has_many_and_belongs_to_many.pdf which I hope you can use – Mar 21 '11 at 21:54
-
That was written 5.5 years ago! I doubt it works in Rails 3. – Jonas Elfström Mar 21 '11 at 21:57
-
What exactly are you looking for? Many-to-many display can get tricky. I don't think it'd be feasible to generate something. – Srdjan Pejic Mar 21 '11 at 22:01
-
Not scaffolding but otherwise a very nice and in-depth answer can be found here http://stackoverflow.com/questions/2168442/many-to-many-relationship-with-the-same-model-in-rails/2168528#2168528 it could even be considered being a tutorial. – Jonas Elfström Mar 21 '11 at 22:02
2 Answers
This tut i have written while creating the below testapp step by step using ruby 1.9.2 on Rails 3.0.5. Also see 'Gemfile' for the gems I used (whole Testapp downloadable, link is at the end in part 15). So here goes:
1) go to a place where you want to create a test app, then
rails new mynewtestapp
cd mynewtestapp
2) then add 2 models that have a has_and_belongs_to_many association
rails g scaffold book title:string author:string
rails g scaffold user name:string age:integer
3) then you need to create the join-table for that asssociation... by default rails will look for a table with the name consisting of the names both associated tables in alphabetical order... so lets create a migration to create such a table
rails g migration createBooksUsers
4) open the generated migration file, which at that point looks like
class CreateBooksUsers < ActiveRecord::Migration
def self.up
end
def self.down
end
end
5) modify that to look like this
class CreateBooksUsers < ActiveRecord::Migration
def self.up
create_table :books_users, :id => false do |t|
t.integer :book_id
t.integer :user_id
end
end
def self.down
drop_table :books_users
end
end
6) add the has_and_belongs_to_many association to the book and user models, as well as the new ids added by the relationship
app/model/book.rb
class Book < ActiveRecord::Base
attr_accessible :title, :author, :user_ids
has_and_belongs_to_many :users
end
app/model/user.rb
class User < ActiveRecord::Base
attr_accessible: :name, :age, :book_ids
has_and_belongs_to_many :books
end
7) now our models and migrations are done ... lets create the tables
rake db:create
rake db:migrate
(well create might not be necessary if you use sqlite3 or if you have created the database to be used manually, this example will work using sqlite therfore i have not added anything related to installing a database-management-system. but as there are plenty and actually all worthy enough to be used are very well documented, you will find any help about that pretty quick)
8) now decide which object shall be assigned to which object.... of course you can do that both ways... i'll keep it simple and demonstrate that to one... lets say you have only a few users and want to assign those to the books...
at this point, i would say lets get some outside help, like binary x suggested... but for simplicity i'd choose the simple_form gem over Formtastic. I guess everyone has their favorites... but simple_form seems to give you more freedom in css-styling the whole output to your wishes... so lets install simple_form at this point, just do
echo "gem 'simple_form', :git => 'git://github.com/plataformatec/simple_form.git'" >> Gemfile
to add simple_form to your Gemfile, then run
bundle install
and install simple form to your application (i. e. generate config, default styles and language files) by
rails g simple_form:install
9) time to modify our books form
the books form right now should look like this
app/views/books/_form.html.erb
01 <%= form_for(@book) do |f| %>
02 <% if @book.errors.any? %>
03 <div id="error_explanation">
04 <h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>
05
06 <ul>
07 <% @book.errors.full_messages.each do |msg| %>
08 <li><%= msg %></li>
09 <% end %>
10 </ul>
11 </div>
12 <% end %>
13
14 <div class="field">
15 <%= f.label :title %><br />
16 <%= f.text_field :title %>
17 </div>
18 <div class="field">
19 <%= f.label :author %><br />
20 <%= f.text_field :author %>
21 </div>
22 <div class="actions">
23 <%= f.submit %>
24 </div>
25 <% end %>
Using simple_form, we can just replace some of the above code (lines 1 and 14 - 24) so the whole file would look like this:
01 <%= simple_form_for(@book) do |f| %>
02 <% if @book.errors.any? %>
03 <div id="error_explanation">
04 <h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>
05
06 <ul>
07 <% @book.errors.full_messages.each do |msg| %>
08 <li><%= msg %></li>
09 <% end %>
10 </ul>
11 </div>
12 <% end %>
13
14 <%= f.input :title %>
15 <%= f.input :author %>
16 <%= f.association :users %>
17
18 <%= f.button :submit %>
19
20 <% end %>
10) Now you may want to start your application
rails s
add some users, then add a book and and there is your first has_and_belongs_to_many form:
11) Well that might not yet be the most beautiful thing to look at, but a simple addition of a stylesheet will help a bit... create a new file
public/stylesheets/simple_form.css
and paste the following lines into it
/* public/stylesheets/simple_form.css */
.simple_form label {
float: left;
width: 100px;
text-align: right;
margin: 2px 10px;
}
.simple_form div.input {
margin-bottom: 10px;
}
.simple_form div.boolean, .simple_form input[type='submit'] {
margin-left: 120px;
}
.simple_form div.boolean label, .simple_form label.collection_radio, .simple_form label.collection_check_boxes{
float: none;
margin: 0;
}
.simple_form .error {
clear: left;
margin-left: 120px;
font-size: 12px;
color: #D00;
display: block;
}
.simple_form .hint {
clear: left;
margin-left: 120px;
font-size: 12px;
color: #555;
display: block;
font-style: italic;
}
Then reload the page and ... Tadaa ... first strike...
12) And if you don't like multiple-choice-listboxes just go back to the books form
app/views/books/_form.html.erb
and modify line
15 <%= f.input :author %>
slightly to
15 <%= f.input :author, :as => :check_boxes %>
to make check-boxes out of the list-box.... but... ewww.... look at this:
13) something seems slightly wrong... the left to right presentation of the options is known to trouble simple_form greenhorns every now and then, but actually its an easy to fix issue
and on top of that little format issue, you might also want to see the Users age behind his name in braces, like 'Tom (25)'
... so lets do 3 quick fixes
a) uncomment and set 2 options in config/initializers/simple_form.rb in order to wrap each checkbox with a div and to place the set of checkboxes inside a fieldset
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
config.collection_wrapper_tag = :fieldset
# You can wrap each item in a collection of radio/check boxes with a tag, defaulting to none.
config.item_wrapper_tag = :div
b) modify our simple_form.css stylesheet a little, as in add:
fieldset { border: 0; }
... unless you'd prefer a big ugly border surrounding the fieldset
c) create the method 'to_label' in our user-model, as 'to_label' is by default the first method simple_form looks for in order to get a String-representation to display an object. By a strange incident our model User has a column called 'name'. As name also is a method simple_form looks for in a model we were lucky this app has worked so far. If we had called the name column forename instead, Rails would have listed not the user names but the default-ruby-object representations (e. g. <#User:521369846>). Guess we were lucky ;-)
app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :users
def to_label
"#{name} (#{age})"
end
end
and the edit-form gets a nice look...
14) Now only the show view needs to display the book owners... thats not too hard either, just open the show-view
app/views/books/show.html.erb
and add lines 13-16 to display the bookowners:
01 <p id="notice"><%= notice %></p>
02
03 <p>
04 <b>Title:</b>
05 <%= @book.title %>
06 </p>
07
08 <p>
09 <b>Author:</b>
10 <%= @book.author %>
11 </p>
12
13 <p>
14 <b>Who owns a copy?</b>
15 <%= @book.users.map {|x| x.to_label}.join ', ' %>
16 </p>
17
18 <%= link_to 'Edit', edit_book_path(@book) %> |
19 <%= link_to 'Back', books_path %>
and last but not least ... the show view
15) Well, so much for a quick tutorial to habtm or in words has_and_belongs_to_many associations in rails. I have put my test-app I created while writing this online at https://1drv.ms/u/s!Alpu50oGtUZq7AiJkL08QqBiMAjb

- 1,805
- 18
- 31
-
-
Is this still valid in rails 3.2.3? I feel like I've followed steps correctly but I'm getting an error after step 11 (when trying to create a book) saying "Can't mass-assign protected attributes"). If I'm just doing it wrong, please ignore. :) – timfreilly May 07 '12 at 23:13
-
@timfreilly: i haven't tried this on rails 3.2.3, but as "everything is new", i. e. Rails and Simple-Form, i would guess that a few things indeed are different. Sadly i haven't had the opportunity to work with Rails 3.1+, so i can't say. Try to google the error message you mentioned, i am sure you'll find a lead towards a solution. – Ingo May 20 '12 at 11:53
-
2In case this comes up, Rails 3.2.3 whitelists attributes for security reasons. This means you can't do a blanket update_attributes. You just need to add "attr_accessible :x, :y" where :x and :y are any fields you want to update from a view to the model you want to update. – Joe Pym Sep 17 '12 at 15:23
Watch Ryan Bate's tutorial on Token Fields:
http://railscasts.com/episodes/258-token-fields
It's a simple way to scaffold a many to many relationship in rails

- 27,713
- 23
- 122
- 168