Recipe 5.10. Processing Dynamically Created Input Fields


Problem

You want to create and process a form consisting of dynamically created input fields. For example, you have a table of users who can each be associated with one or more roles. Both the users and the roles come from a database; new users and roles can be added at any time. You want to be able to manage the relationship between users and roles.

Solution

Sometimes the easiest way to administer such a relationship is with a table consisting of checkboxes, one for each possible relationship between the two models.

Start by creating tables containing users and roles, as well as a permissions table to store associations:

db/schema.rb:

ActiveRecord::Schema.define(:version => 0) do   create_table "roles", :force => true do |t|     t.column "name",    :string,  :limit => 80   end   create_table "users", :force => true do |t|     t.column "login",   :string,  :limit => 80   end   create_table "permissions", :id => false, :force => true do |t|     t.column "role_id", :integer, :default => 0, :null => false     t.column "user_id", :integer, :default => 0, :null => false   end end

For added flexibility in manipulating the data in the join table, create the many-to-many relationship using the has_many :through option:

class Role < ActiveRecord::Base   has_many :permissions, :dependent => true    has_many :users, :through => :permissions end class User < ActiveRecord::Base   has_many :permissions, :dependent => true    has_many :roles, :through => :permissions end class Permission < ActiveRecord::Base   belongs_to :role   belongs_to :user end

Create a User Controller with actions to list and update all possible associations between users and roles:

app/controllers/user_controller.rb:

class UserController < ApplicationController   def list_perms     @users = User.find(:all, :order => "login")     @roles = Role.find(:all, :order => "name")    end   def update_perms     Permission.transaction do       Permission.delete_all       for user in User.find(:all)         for role in Role.find(:all)           if params[:perm]["#{user.id}-#{role.id}"] == "on"              Permission.create(:user_id => user.id, :role_id => role.id)           end              end            end          end     flash[:notice] = "Permissions Updated."     redirect_to :action => "list_perms"   end end

Next, create a view for the list_perms action that builds a form containing a table, with checkboxes at the intersection of each user and role:

app/views/user/list_perms.rhtml:

<h2>Administer Permissions</h2> <% if flash[:notice] -%>   <p style="color: red;"><%= flash[:notice] %></p> <% end %> <% form_tag :action => "update_perms" do %> <table style="background: #ccc;">   <tr>     <th>&nbsp;</th>     <% for user in @users %>       <th><%= user.login %></th>      <% end %>   </tr> <% for role in @roles %>   <tr style="background: <%= cycle("#ffc","white") %>;">     <td align="right"><strong><%= role.name %></strong></td>     <% for user in @users %>       <td align="center">         <%= get_perm(user.id, role.id) %>       </td>        <% end %> <% end %> </table> <br /> <%= submit_tag "Save Changes" %> <% end %>

The get_perm helper method used in the list_perms view builds the HTML for each checkbox in the form. Define get_perm in user_helper.rb:

app/helpers/user_helper.rb:

module UserHelper   def get_perm(role_id, user_id)     name = "perm[#{user_id}-#{role_id}]"     perm = Permission.find_by_role_id_and_user_id(role_id, user_id)     color = "#f66"     unless perm.nil?       color = "#9f9"       checked = 'checked=\"checked\"'     end     return "<span style=\"background: #{color};\"><input name=\"#{name}\"                    type=\"checkbox\" #{checked}></span>"   end   end

Discussion

The solution starts by creating a many-to-many association between the users and roles tables using the has_many :through method of Active Record. This allows you to manipulate data in the permissions table as well as take advantage of the transaction method of the Permission class.

With the relationship between the tables set up, the User controller stores all user and role objects into instance variables that are available to the view. The list_perms view starts with a loop that iterates over users, displaying them as column headings. Next, a table of user permissions is created by looping over roles, which become the table's rows, with a second loop iterating over users, one per column.

The form consists of dynamically created checkboxes at the intersection of every user and role. Each checkbox is identified by a string combining the user.id and role.id strings (perm[#{user_id}-#{role_id}]). When the form is submitted, params[:perm] is a hash that contains each of these user.id/role.id pairs. The contents of this hash look like this:

irb(#<UserController:0x405776a0>):003:0> params[:perm] => {"2-2"=>"on", "2-3"=>"on", "1-4"=>"on", "2-4"=>"on", "1-5"=>"on",      "4-4"=>"on", "5-3"=>"on", "4-5"=>"on", "5-4"=>"on", "1-1"=>"on"}

The update_perms action of the User controller starts by removing all existing Permission objects. Because something may cause the rest of this action to fail, all the code that could alter the database is wrapped in an Active Record transaction. This transaction ensures that deleting a user/role association is rolled back if something fails later in the method.

To process the values of the checkboxes, update_perms reproduces the nested loop structure that created the checkbox element in the view. As each checkbox name is reconstructed, it's used to access the value of the hash that is stored using that name as a key. If the value is on, the action creates a Permissions object that associates a specific user with a role.

The view uses color to indicate which permissions existed before the user changes any of the selected permissions: green for an association and red for a lack of one.

Figure 5-7 shows the matrix of input fields created by the solution.

Figure 5-7. A form containing a matrix of checkboxes generated dynamically


See Also

  • Section 5.12"




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