RESTful Rails with Associations explored

Posted by blackrat on December 16, 2006

I’ve recently switched to EdgeRails for Rails 1.2 RC1, and decided to try out the resource_scaffold to create RESTful classes. There’s not that much documentation on this so far, so I thought I’d combine using these with creating habtm associations. What follows is a walkthrough of the initial creation of a has_and_belongs_to_many association and the changes required to get it to do what I want within the scaffold_resource RESTful framework.

First of all, some MVC creation. I like the fact that tests are now generated by the scaffold_resource, but be warned that if you modify your migrations, you also need to change the test/fixtures/*.yml files to reflect the changes. Otherwise, your unit tests will start to error. We’re going to be using the extension to scaffold_resource and generate a string “name” for both tables, which will also generate the fixture unit test for that column.

rails –database=sqlite3 blog
cd blog
script/generate scaffold_resource user name:string
script/generate scaffold_resource tag name:string
script/generate migration create_tags_users
Change db/migration/003_create_tags_users.rb to the following:
class CreateTagsUsers < ActiveRecord::Migration
    def self.up
        create_table :tags_users, :id=>false do |t|
            t.column :tag_id, :integer
            t.column :user_id, :integer
        end
    end
    def self.down
        drop_table :tags_users
    end
end

Don’t forget the :id=>false (unless you know what you are doing, that is).
Next, modify the models to point to each other. app/models/tag.rb and app/models/user.rb become:

class Tag < ActiveRecord::Base
    has_and_belongs_to_many :users
end

and

class User < ActiveRecord::Base
    has_and_belongs_to_many :tags
end

At this point, I like to run the migrate and the tests which were created by the scaffold_resource.

rake db:migrate test
Now it’s time for a little association plumbing. I like to have a view to allow me to select the tags from the full list (we’ll ignore adding more from this view for this walkthrough) when in the user display, so the first port of call is the users_controller. I’d also like to be able to see from the index display which ones are selected, so I’ll be modifying the new, edit, create and update methods and all of the views for user.

Ok. For app/views/users the following become the new views

index.rhtml

<h1>Listing users</h1>
<table>
    <tr>
          <th>Name</th>
          <th>Tags</th>
    </tr>
    <% for user in @users %>
          <tr>
               <td><%=h user.name %></td>
               <td>
                    <% for tag in user.tags %>
                         <%= tag.name %><br />
                    <% end %>
               </td>
               <td><%= link_to 'Show', user_path(user) %></td>
               <td><%= link_to 'Edit', edit_user_path(user) %></td>
               <td><%= link_to 'Destroy', user_path(user), :confirm => 'Are you sure?', :method => :delete %></td>
        </tr>
    <% end %>
</table>
<br />
<%= link_to 'New user', new_user_path %>

new.rhtml

<h1>New user</h1>
<%= error_messages_for :user %>
<% form_for(:user, :url => users_path) do |f| %>
    <p>
          <b>Name</b><br />
          <%= f.text_field :name %>
     </p>
    <p>
        <b>Tags</b><br />
          <% for tag in @tags %>
               <br />
            <input type="checkbox"
                            id="<%=tag.id%>"
              name="tag_ids[]"
                            value="<%=tag.id%>"
                        >
            <%=tag.name%>
        <% end %>
    </p>
     <p>
          <%= submit_tag "Create" %>
     </p>
<% end %>
<%= link_to 'Back', users_path %>

show.rhtml

<p>
    <b>Name:</b>
     <%=h @user.name %>
</p>
<p>
    <b>Tags:</b>
     <% for tag in @user.tags %>
          <%= tag.name %><br />
     <% end %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

and finally, edit.rhtml

<h1>Editing user</h1>
<%= error_messages_for :user %>
<% form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %>
    <p>
          <b>Name</b><br />
          <%= f.text_field :name %>
     </p>
     <p>
          <b>Tags</b><br />
          <% for tag in @tags %>
            <br />
               <input type="checkbox"
                   id="<%=tag.id%>"
                   name="tag_ids[]"
                   value="<%=tag.id%>"
                   <%if @user.tags.include? tag%>checked="checked"<%end%>
               >
               <%=tag.name%>
          <% end %>
     </p>
     <p>
          <%= submit_tag "Update" %>
     </p>
<% end %>
<%= link_to 'Show', user_path(@user) %> |
<%= link_to 'Back', users_path %>

So not that much different to the CRUD scaffolding. I’m going to experiment more with this, and also with the through :class approach of handling associations, which I believe looks to have some strong advantages (not the least of which being that it gives you a join class rather than a join table).

Trackbacks

Use this link to trackback from your own site.

Comments

You must be logged in to leave a response.