Forum: Ruby on Rails HABTM - two lists

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cad27a22166c3a46e34da899476ec897?d=identicon&s=25 Ryan (Guest)
on 2006-04-21 15:13
Does anyone know of a good example somewhere of an implemention for
adding records with HABTM relations that have lots of possibilities.
The best way that I've seen it done is where you have two lists
(multiple select lists) where you have the possibilities on the left and
ones you've added on the right with arrows to add and delete items.  But
the only ways I know how to implement that solution are ugly (because
the form will only submit selected items from the right).

Does anyone know how to implement the two lists elegantly or of another
solution that works well for many items?
C31c7255fa4488dfc5d766403b497a36?d=identicon&s=25 Jim Morris (wolfmanjm)
on 2006-04-22 23:00
(Received via mailing list)
I used a tree.js (http://www.silverstripe.com/downloads/tree/)
controller as my lists
where somewhat hierarchical so it cut down on the screen real-estate for
very large lists.

I put two trees on the page side by side, the left tree is what I
currently have and
what I can choose from on the right.

I added check boxes to the right hand tree nodes, so I select the ones I
want and then
click a button below that basically replaces the items on the left side
with the ones
selected on the right.

Example code explains it better I guess :)
This example sets up roles and rights for a standard RBAC.

Rights has_and_belongs_to_many :roles
Roles  has_and_belongs_to_many :rights

I setup the data by creating hashes in the controller which are
available to the view...

 # roll everything up into a hierarchical tree structure
 # top level is hash of roles
 #   next level is hash of permissions with controller as key and array
of actions as value
        @role_tree= {}
        roles= Role.find_all
        roles.each do |r|
            ph= {}
            rights= r.rights
            rights.each do |p|
                ph[p.controller] ||= []
                ph[p.controller] << p.action
            end
            @role_tree[r.name] = ph
        end

        # creates a tree of controllers with the actions for each
controller
        # top level is hash of controllers
        #  next level is an array of actions
        @rights_tree= {}
        rights= Right.find_all
        rights.each do |r|
            name= r.controller
            @rights_tree[name] ||= []
            @rights_tree[name] << r.action
        end
    end


The view is simple... (edit.rhtml)
<h1>Role and Rights Editor</h1>

<table cellspacing="10" cellpadding="20%">
<tr>
<th align="left">Roles</th>
<th align="left">Rights</th>
<tr>
<td valign="top"><%= render_role_tree(@role_tree) %></td>
<td><%= start_form_tag :action => 'edit' %>
   <%= render_rights_tree(@rights_tree) %>
<p>
The checked items above will replace the Rights of the following
Roles:<br>
   <% @role_tree.each_key do |role| %>
        <%= check_box_tag "role", role %> <%= role %>
   <% end %>
<p>
   <%= submit_tag "Allocate to selected Roles" %>
<%= end_form_tag %>
</td>
</table>

This part goes in helpers, and creates the html for the two trees this
is specific for
the silverstripe tree control)...

module RoleHelper
    def render_role_tree(tree)
        ret = ''
        ret += "<ul class='tree'>"

        tree.each_key do |r|
            ret += '<li><a href="#">' + r + '</a>' # list roles
            h= tree[r] # get hash of controllers/[actions]
            unless h.empty?
                ret += '<ul>'
                h.each_key do |c|
                    ret += '<li><a href="#">' + c + '</a>' # list
controllers
                    a= h[c]
                    unless a.empty?
                       ret += '<ul>'
                       a.each do |an|
                            ret += '<li><a href="#">' + an + '</a></li>'
# list actions
                       end
                       ret += '</ul>'
                    end
                    ret += '</li>'
                end
                ret += '</ul>'
            end
            ret += '</li>'
        end
        ret += '</ul>'
        ret
    end

    def render_rights_tree(tree)
        ret = ''
        ret += "<ul class='tree'>"

        tree.each_key do |r|
            ret += "<li><a href=\"#\">#{r}</a>"# list controllers
            aa= tree[r] # get array of actions
            unless aa.empty?
                ret += '<ul>'
                ret += "<li><a href=\"#\"
onclick=\"checkAll('right[#{r}][]'); return
false;\">Check All</a>"
                aa.each do |a|
                    ret += '<li>' + "<input type=\"checkbox\"
name=\"right[#{r}][]\"
value=\"#{a}\" />" + a + '</li>' # list actions
                end
                ret += '</ul>'
            end
            ret += '</li>'
        end
        ret += '</ul>'
        ret
    end
end

the action handler in the controller for the post is a little more
complicated in my
case, but basically goes through all the selected checkboxes and sets
the relevant habtms.

   def edit
        if request.post?
            #p params
            unless params['role'].nil?
                a_roles= params['role']
                a_rights= params['right']
                unless a_rights.nil?
                    a_roles.each { |ar|
                        role= Role.find_by_name(ar)
                        unless role.nil?
                            role.rights.clear
                            a_rights.each { |controller, aa|
                                aa.each { |action|
                                    right=
Right.find_by_controller_and_action(controller, action)
                                    unless right.nil?
                                        role.rights << right
                                        #puts "Allocating right
#{controller}/#{action}
to #{ar}"
                                    else
                                        puts "Right
#{controller}/#{action} not found"
                                    end
                                }
                            }
                        else
                            puts "Role #{ar} not found"
                        end
                    }
                    flash[:notice]= 'rights updated'
                else
                    flash[:warning]= 'no rights were selected'
                end
            else
                flash[:warning]= 'You need to select the role to
allocate to'
            end
        end

end

Hope that helps and is not too complex :)
This topic is locked and can not be replied to.