Ruby on Rails
HowtoUseFormOptionHelpers (Version #69)

Creating Dynamic Drop-Downs

    There are two ways to dynamically create HTML ‘select’ code.
    *collection_select is for new forms, with no previous data. *collection_select_with_current (This is outdated. You can just use collectionselect. If it is already set, it will select the correct option for you_) is for existing forms that need to pre-select an item based on data being edited

    h2. Drop-down w/o Selected Item

    use collection_select

    <%= collection_select("job", "client_id" , @clients, "id", "name") %>


    job is the object (model row) and client_id is the column of job to set. @clients is a collection of Client objects perhaps read from a table of clients, id is the client column whose value will be used to set the client_id and name is the client column that contains the text to be displayed.

    (You can use clients = Client.find_all to get the clients collection or you can put the "Client.find_all" directly into the collection_select instead of "clients". Like this <%= collection_select :Job, :client_id, Client.find(:all), :id, :name %>).

    This would create:

    <select name="job[client_id]">
     <option value="1">Francisco Hernandez
     
    

    Drop-down with or without selected item

    select() is meant to operate on ActiveRecord object instances, or, more generally, any class instance which has read accessor methods, à la attr_reader.

    ex1:

    select (:artists, :name, @deadArtists)

    will create a drop-down select list for all of the artists in @deadArtists. Each deadArtist is checked to see if they match the value of @artists.name. If they match, they will be selected in the created drop-down list.

    You say you want to use some other data structure – a hash or an array – and you are determined to use the select() helper function? You can either force your data into a data structure that can be accessed via the dot-accessor (e.g., by using the Struct object), or you can use the :selected option.

    ex2:

    
    #controller:
        @SiteLanguages = ['en', 'de', 'fr']
    ...
        pLanguages =  params[:languages]
        @languages = Struct.new(:language).new(pLanguages['language'])

    #view:
    <%= select :languages, :language, @SiteLanguages %>

    The compact single-line syntax above could also be written as:

    
    MyStruct = Struct.new(:language)
    @languages = new MyStruct(pLanguages['language'])
    


    ex3:
    Using the :selected option

    
        @SiteLanguages = ['en', 'de', 'fr']
        @selected_language = 'en'
    
    #view:
        <%= select :languages, :language, @SiteLanguages, {:selected=>@selected_language} %>
    

    You CAN use “options_from_collection_for_select” where the last argument is an integer to match the id of the selected item.

    Drop-down w/selected item

    I frequently need select boxes that properly mark the current value when editing. The above conversation/points didn’t make the issue any clearer to me. I put together the following helper method to save me some time. Forgive me if there are better ways to do this, cleaner ways to build the result, etc.

    def collection_select_with_current(object, method, collection, value_method, text_method, current_value)
      result = "<select name='#{object}[#{method}]'>\n"
      for element in collection
        if current_value == element.send(value_method)
          result << "<option value='#{element.send(value_method)}' selected='selected'>#{element.send(text_method)}</option>\n"
        else
          result << "<option value='#{element.send(value_method)}'>#{element.send(text_method)}</option>\n"
        end
      end
      result << "</select>\n"
      return result
    end
    

    The usage in a view would look something like…

    
    <%= collection_select_with_current('page','section_id',@sections,'id','name',@page.section_id) %>
    

    Note that this code doesn’t include a bunch of the extra feature (html options and so forth). I’ve never needed them.

    note: the only way I could get this to work was by adding .tos to the element comparison. Like so:

    
    if currentvalue == element.send(value_method).to_s

    How would one go about adding html options to collectionselect_with_current?_

    select helper

    I used the select helper to give the user a choice to select the sex of a person.

    The helper code is as follows:

    <%= select( "member", "sex", { "male" => "M", "female" => "F"}) %>
    

    This would render this HTML:

    <select id="member_sex" name="member[sex]">
    <option value="F">female

    Rails would assign the values M or F for this attribute in the database, and pre-select the right value if the view is rendered for editing.

    (Why does the hash get sorted, F before M? This isn’t significant here but it’s a problem with what I’m using (a huge list that needs to remain in a fixed order)
    You can never be guaranteed of any order with hashes. You could try .sort() to sort the values in alphabetical order.

    options_from_collection_for_select and multiple selected values

    When trying to get multiple selected values from a <SELECT> tag into @params, call your parameter something with [] on the end. That way rails knows it’s multiple.

    So instead of

    <select name="job[techs]" size="5" multiple="multiple">
    </pre>

    Use:

    <select name="job[techs][]" size="5" multiple="multiple">
    </pre>

    Example

    <% @selected = @job.technician.collect { |t| t.technician_id.to_i } %>
    <select name="job[techs][]" size="5" multiple="multiple">
      <%= options_from_collection_for_select(@techs, "id", "full_name", @selected) %>
    </select>

    Assigning to has_many or has_and_belongs_to_many collections

    If you want to have your multiple choice select assign to a has_many or has_and_belongs_to_many (habtm) collection on your object, you should specify the collection using the <collection name>_ids name. For example, if we have an AddressGroup object with a collection of Address objects called addresses, we would not use this:

    
    <select name="addressgroup[addresses][]" multiple="multiple">
    

    but rather this:

    
    <select name="addressgroup[address_ids][]" multiple="multiple">
    

    With either select statement the returned value is an array of Address object ids, not the actual array objects, which will fail if we tried to assign to addressgroup.addresses. If you try to do that you will get an error message like “Expected Address and got String”. Instead, we use the implicit address_ids= property (automatically added to an object when it has a has_many or habtm collection), which assigns to the collection based on a collection of IDs.


    options_from_collection_for_select with custom text

    Sometimes you may want to use multiple attributes for the text portions of the select list. or maybe you want to munge a value in some specific way.

    <select id="device_outlet" name="device[outlet]">
      <option value=""></option>
      <%= options_from_collection_for_select @outlets.collect {|outlet| [outlet.id, outlet.powerstrip.name + ":" + outlet.name]}, 'first', 'last', @selected_outlet %>
      </select>

    By creating a collection of arrays with 2 values (the first being the option value and the second being the option text) you can munge/change/concatenate to your hearts content. The trick is use the first and last methods for an Array.

    If you want you can always sort by the option text as well:

    <select id="device_outlet" name="device[outlet]">
      <option value=""></option>
      <%= options_from_collection_for_select @outlets.collect {|outlet| [outlet.id, outlet.powerstrip.name + ":" + outlet.name]}.sort{|x,y| x.last <=> y.last}, 'first', 'last', @selected_outlet %></select>

    — Joel Nimety

    Or just add a method to the model that concatenates the fields and use that in the call to collection_for_select.


    collection_select_with_current with html_options

    To answer the question “How would one go about adding html options to collection_select_with_current?” Try this:

    def collection_select_with_current(object, method, collection, value_method, text_method, current_value, html_options={})
    	result = "<select name='#{object}[#{method}]'"
    	html_options.each do |key, value|
    		result << ' ' + key.to_s + '="' + value.to_s + '"'
    	end
    	result << ">\n"
    	for element in collection
    		if current_value == element.send(value_method)
    			result << "<option value='#{element.send(value_method)}' selected='selected'>#{element.send(text_method)}</option>\n" 
    		else
    			result << "<option value='#{element.send(value_method)}'>#{element.send(text_method)}</option>\n" 
    		end
    	end
    	result << "</select>\n" 
    	return result
      end
    

    Use html_options like so:

    
    collection_select_with_current('entry', 'author_id', @authors, 'id', 'full_name', @entry.author_id, {:class => 'my_select'})
    

    Or leave out the html_options like this:

    
    collection_select_with_current('entry', 'author_id', @authors, 'id', 'full_name', @entry.author_id)
    

    — Chris Your


    Drop-down with multiple selected items

    Here is my, and hopefully the best, solution:

    user.rb:

    
    class User < ActiveRecord::Base
      has_and_belongs_to_many :roles
    end
    

    role.rb:

    
    class Role < ActiveRecord::Base
      has_and_belongs_to_many :users
    end
    

    user_controller.rb:

    
    class UserController < ApplicationController
      def new
        @user = User.new
        @roles = Role.find(:all)
      end
    ...
      def edit
        @user = User.find(params[:id])
        @roles = Role.find(:all)
      end
    end
    

    And in *.rhtml, i use:

    
    <%= collection_select('user', 'role_ids', @roles, :id, :name, {}, :multiple => true) %>
    

    — Christian Mueller


    How to pass in disabled:
    It appears that select() takes these options:

    def select(object, method, choices, options = {}, html_options = {})

    So if you want it disabled, you’ll need to pass that in last, or
    select(:abc, :option_name, ‘yes’, true?, {}, {:disabled => true }) to have a disabled version.

Creating Dynamic Drop-Downs

    There are two ways to dynamically create HTML ‘select’ code.
    *collection_select is for new forms, with no previous data. *collection_select_with_current (This is outdated. You can just use collectionselect. If it is already set, it will select the correct option for you_) is for existing forms that need to pre-select an item based on data being edited

    h2. Drop-down w/o Selected Item

    use collection_select

    <%= collection_select("job", "client_id" , @clients, "id", "name") %>


    job is the object (model row) and client_id is the column of job to set. @clients is a collection of Client objects perhaps read from a table of clients, id is the client column whose value will be used to set the client_id and name is the client column that contains the text to be displayed.

    (You can use clients = Client.find_all to get the clients collection or you can put the "Client.find_all" directly into the collection_select instead of "clients". Like this <%= collection_select :Job, :client_id, Client.find(:all), :id, :name %>).

    This would create:

    <select name="job[client_id]">
     <option value="1">Francisco Hernandez
     
    

    Drop-down with or without selected item

    select() is meant to operate on ActiveRecord object instances, or, more generally, any class instance which has read accessor methods, à la attr_reader.

    ex1:

    select (:artists, :name, @deadArtists)

    will create a drop-down select list for all of the artists in @deadArtists. Each deadArtist is checked to see if they match the value of @artists.name. If they match, they will be selected in the created drop-down list.

    You say you want to use some other data structure – a hash or an array – and you are determined to use the select() helper function? You can either force your data into a data structure that can be accessed via the dot-accessor (e.g., by using the Struct object), or you can use the :selected option.

    ex2:

    
    #controller:
        @SiteLanguages = ['en', 'de', 'fr']
    ...
        pLanguages =  params[:languages]
        @languages = Struct.new(:language).new(pLanguages['language'])

    #view:
    <%= select :languages, :language, @SiteLanguages %>

    The compact single-line syntax above could also be written as:

    
    MyStruct = Struct.new(:language)
    @languages = new MyStruct(pLanguages['language'])
    


    ex3:
    Using the :selected option

    
        @SiteLanguages = ['en', 'de', 'fr']
        @selected_language = 'en'
    
    #view:
        <%= select :languages, :language, @SiteLanguages, {:selected=>@selected_language} %>
    

    You CAN use “options_from_collection_for_select” where the last argument is an integer to match the id of the selected item.

    Drop-down w/selected item

    I frequently need select boxes that properly mark the current value when editing. The above conversation/points didn’t make the issue any clearer to me. I put together the following helper method to save me some time. Forgive me if there are better ways to do this, cleaner ways to build the result, etc.

    def collection_select_with_current(object, method, collection, value_method, text_method, current_value)
      result = "<select name='#{object}[#{method}]'>\n"
      for element in collection
        if current_value == element.send(value_method)
          result << "<option value='#{element.send(value_method)}' selected='selected'>#{element.send(text_method)}</option>\n"
        else
          result << "<option value='#{element.send(value_method)}'>#{element.send(text_method)}</option>\n"
        end
      end
      result << "</select>\n"
      return result
    end
    

    The usage in a view would look something like…

    
    <%= collection_select_with_current('page','section_id',@sections,'id','name',@page.section_id) %>
    

    Note that this code doesn’t include a bunch of the extra feature (html options and so forth). I’ve never needed them.

    note: the only way I could get this to work was by adding .tos to the element comparison. Like so:

    
    if currentvalue == element.send(value_method).to_s

    How would one go about adding html options to collectionselect_with_current?_

    select helper

    I used the select helper to give the user a choice to select the sex of a person.

    The helper code is as follows:

    <%= select( "member", "sex", { "male" => "M", "female" => "F"}) %>
    

    This would render this HTML:

    <select id="member_sex" name="member[sex]">
    <option value="F">female

    Rails would assign the values M or F for this attribute in the database, and pre-select the right value if the view is rendered for editing.

    (Why does the hash get sorted, F before M? This isn’t significant here but it’s a problem with what I’m using (a huge list that needs to remain in a fixed order)
    You can never be guaranteed of any order with hashes. You could try .sort() to sort the values in alphabetical order.

    options_from_collection_for_select and multiple selected values

    When trying to get multiple selected values from a <SELECT> tag into @params, call your parameter something with [] on the end. That way rails knows it’s multiple.

    So instead of

    <select name="job[techs]" size="5" multiple="multiple">
    </pre>

    Use:

    <select name="job[techs][]" size="5" multiple="multiple">
    </pre>

    Example

    <% @selected = @job.technician.collect { |t| t.technician_id.to_i } %>
    <select name="job[techs][]" size="5" multiple="multiple">
      <%= options_from_collection_for_select(@techs, "id", "full_name", @selected) %>
    </select>

    Assigning to has_many or has_and_belongs_to_many collections

    If you want to have your multiple choice select assign to a has_many or has_and_belongs_to_many (habtm) collection on your object, you should specify the collection using the <collection name>_ids name. For example, if we have an AddressGroup object with a collection of Address objects called addresses, we would not use this:

    
    <select name="addressgroup[addresses][]" multiple="multiple">
    

    but rather this:

    
    <select name="addressgroup[address_ids][]" multiple="multiple">
    

    With either select statement the returned value is an array of Address object ids, not the actual array objects, which will fail if we tried to assign to addressgroup.addresses. If you try to do that you will get an error message like “Expected Address and got String”. Instead, we use the implicit address_ids= property (automatically added to an object when it has a has_many or habtm collection), which assigns to the collection based on a collection of IDs.


    options_from_collection_for_select with custom text

    Sometimes you may want to use multiple attributes for the text portions of the select list. or maybe you want to munge a value in some specific way.

    <select id="device_outlet" name="device[outlet]">
      <option value=""></option>
      <%= options_from_collection_for_select @outlets.collect {|outlet| [outlet.id, outlet.powerstrip.name + ":" + outlet.name]}, 'first', 'last', @selected_outlet %>
      </select>

    By creating a collection of arrays with 2 values (the first being the option value and the second being the option text) you can munge/change/concatenate to your hearts content. The trick is use the first and last methods for an Array.

    If you want you can always sort by the option text as well:

    <select id="device_outlet" name="device[outlet]">
      <option value=""></option>
      <%= options_from_collection_for_select @outlets.collect {|outlet| [outlet.id, outlet.powerstrip.name + ":" + outlet.name]}.sort{|x,y| x.last <=> y.last}, 'first', 'last', @selected_outlet %></select>

    — Joel Nimety

    Or just add a method to the model that concatenates the fields and use that in the call to collection_for_select.


    collection_select_with_current with html_options

    To answer the question “How would one go about adding html options to collection_select_with_current?” Try this:

    def collection_select_with_current(object, method, collection, value_method, text_method, current_value, html_options={})
    	result = "<select name='#{object}[#{method}]'"
    	html_options.each do |key, value|
    		result << ' ' + key.to_s + '="' + value.to_s + '"'
    	end
    	result << ">\n"
    	for element in collection
    		if current_value == element.send(value_method)
    			result << "<option value='#{element.send(value_method)}' selected='selected'>#{element.send(text_method)}</option>\n" 
    		else
    			result << "<option value='#{element.send(value_method)}'>#{element.send(text_method)}</option>\n" 
    		end
    	end
    	result << "</select>\n" 
    	return result
      end
    

    Use html_options like so:

    
    collection_select_with_current('entry', 'author_id', @authors, 'id', 'full_name', @entry.author_id, {:class => 'my_select'})
    

    Or leave out the html_options like this:

    
    collection_select_with_current('entry', 'author_id', @authors, 'id', 'full_name', @entry.author_id)
    

    — Chris Your


    Drop-down with multiple selected items

    Here is my, and hopefully the best, solution:

    user.rb:

    
    class User < ActiveRecord::Base
      has_and_belongs_to_many :roles
    end
    

    role.rb:

    
    class Role < ActiveRecord::Base
      has_and_belongs_to_many :users
    end
    

    user_controller.rb:

    
    class UserController < ApplicationController
      def new
        @user = User.new
        @roles = Role.find(:all)
      end
    ...
      def edit
        @user = User.find(params[:id])
        @roles = Role.find(:all)
      end
    end
    

    And in *.rhtml, i use:

    
    <%= collection_select('user', 'role_ids', @roles, :id, :name, {}, :multiple => true) %>
    

    — Christian Mueller


    How to pass in disabled:
    It appears that select() takes these options:

    def select(object, method, choices, options = {}, html_options = {})

    So if you want it disabled, you’ll need to pass that in last, or
    select(:abc, :option_name, ‘yes’, true?, {}, {:disabled => true }) to have a disabled version.