Recipe 5.4. Editing Many-to-Many Relationships with Multiselect Lists


Problem

You have two models that have a many-to-many relationship to each other. You want to create a select list in the edit view of one model that allows you to associate with one or more records of the other model.

Solution

As part of your application's authentication system, you have users that can be assigned to one or more roles that define access privileges. The many-to-many relationship between users and roles is set up by the following class definitions:

app/models/user.rb:

class User < ActiveRecord::Base   has_and_belongs_to_many :roles end

app/models/role.rb:

class Role < ActiveRecord::Base   has_and_belongs_to_many :users end

In the edit action of the Users controller, add an instance variable named @selected_roles and populate it with all of the Role objects. Define a private method named handle_roles_users to handle updating a User object with associated roles from the params hash.

app/controllers/users_controller.rb:

class UsersController < ApplicationController   def edit     @user = User.find(params[:id])     @roles = {}     Role.find(:all).collect {|r| @roles[r.name] = r.id }   end   def update     @user = User.find(params[:id])     handle_roles_users     if @user.update_attributes(params[:user])       flash[:notice] = 'User was successfully updated.'       redirect_to :action => 'show', :id => @user     else           render :action => 'edit'     end   end   private      def handle_roles_users       if params['role_ids']         @user.roles.clear         roles = params['role_ids'].map { |id| Role.find(id) }         @user.roles << roles        end          end end

In the Users edit view, create a multiple option select list using options_for_select to generate the options from the objects in the @roles instance variable. Construct a list of existing role associations and pass it in as the second parameter.

app/views/users/edit.rhtml:

<h1>Editing user</h1> <% form_tag :action => 'update', :id => @user do %>   <%= render :partial => 'form' %> <p> <select  name="role_ids[]" multiple="multiple">   <%= options_for_select(@roles, @user.roles.collect {|d| d.id }) %> </select> </p>   <%= submit_tag 'Edit' %> <% end %> <%= link_to 'Show', :action => 'show', :id => @user %> | <%= link_to 'Back', :action => 'list' %>

To display the roles associated with each user, join them as a comma-separated list in view of the show action:

app/views/users/show.rhtml:

<% for column in User.content_columns %> <p>   <b><%= column.human_name %>:</b> <%=h @user.send(column.name) %> </p> <% end %> <p>   <b>Role(s):</b> <%=h @user.roles.collect {|r| r.name}.join(', ') %> </p> <%= link_to 'Edit', :action => 'edit', :id => @user %> | <%= link_to 'Back', :action => 'list' %>

Discussion

There are a number of helpers available for turning collections of objects into select lists in Rails. For example, the select or select_tag methods of ActionView::Helpers::FormOptionsHelper will generate the entire HTML select tag based on a number of options. Most of these helper methods generate only the options list.

Figure 5-4 shows two roles selected for a user and how those roles are listed in the view of the show action.

Figure 5-4. A form allowing selection of multiple items from a select list


See Also

  • For more information on options helpers in Rails, see http://www.rubyonrails.org/api/classes/ActionController/Pagination.html

  • Section 3.0," for an introduction to link tables used to manage many-to-many relationships




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net