rails 3 scaffolding of relational model

11,690

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: first simple_form form without special effects

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... after adding a default simple_form stylesheet

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: left to right checkbox display

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... nice and cozy edit view

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 Who owns a book?

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

Share:
11,690
lurscher
Author by

lurscher

Updated on July 19, 2022

Comments

  • lurscher
    lurscher almost 2 years

    are there any tutorials out there how to scaffolding a simple model that uses many-to-many relationships?

  • Mike
    Mike about 12 years
    nice write up! quite thorough.
  • timfreilly
    timfreilly almost 12 years
    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. :)
  • Ingo
    Ingo almost 12 years
    @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.
  • Joe Pym
    Joe Pym over 11 years
    In 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.