Ruby on Rails
HowtoUseFormOptionHelpers (Version #73)

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

    To force a selected value, use the :selected value

    
    select(:lead, :months, month_map, {:selected => 6}, {}) # 6 will always be pre-selected
    select(:lead, :months, month_map, {}, {}) # @lead.months [or nothing, if that's nil] will be pre-selected
    

    options_for_select

    Note that there is also a helper options_for_select
    which yields strings like

    
    

    “]


    ”http://api.rubyonrails.com/classes/ActionView/Helpers/FormOptionsHelper.html">http://api.rubyonrails.com/classes/ActionView/Helpers/FormOptionsHelper.html

    default values

    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

    To force a selected value, use the :selected value

    
    select(:lead, :months, month_map, {:selected => 6}, {}) # 6 will always be pre-selected
    select(:lead, :months, month_map, {}, {}) # @lead.months [or nothing, if that's nil] will be pre-selected
    

    options_for_select

    Note that there is also a helper options_for_select
    which yields strings like

    
    

    “]


    ”http://api.rubyonrails.com/classes/ActionView/Helpers/FormOptionsHelper.html">http://api.rubyonrails.com/classes/ActionView/Helpers/FormOptionsHelper.html

    default values

    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.