ruby on rails f.select options with custom attributes - ruby-on-rails

I have a form select statement, like this:
= f.select :country_id, #countries.map{ |c| [c.name, c.id] }
Which results in this code:
...
<option value="1">Andorra</option>
<option value="2">Argentina</option>
...
But I want to add a custom HTML attribute to my options, like this:
...
<option value="1" currency_code="XXX">Andorra</option>
<option value="2" currency_code="YYY">Argentina</option>
...

Rails CAN add custom attributes to select options, using the existing options_for_select helper. You almost had it right in the code in your question. Using html5 data-attributes:
<%= f.select :country_id, options_for_select(
#countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] }) %>
Adding an initial selection:
<%= f.select :country_id, options_for_select(
#countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] },
selected_key = f.object.country_id) %>
If you need grouped options, you can use the grouped_options_for_select helper, like this (if #continents is an array of continent objects, each having a countries method):
<%= f.select :country_id, grouped_options_for_select(
#continents.map{ |group| [group.name, group.countries.
map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] } ] },
selected_key = f.object.country_id) %>
Credit should go to paul # pogodan who posted about finding this not in the docs, but by reading the rails source. https://web.archive.org/web/20130128223827/http://www.pogodan.com/blog/2011/02/24/custom-html-attributes-in-options-for-select

You could do this as follows:
= f.select :country_id, #countries.map{ |c| [c.name, c.id, { 'data-currency-code' => c.currency_code} ] }

This is not possible directly with Rails, and you'll have to create your own helper to create the custom attributes. That said, there are probably two different ways to accomplish what you want:
(1) Using a custom attribute name in HTML5. In HTML5 you are allowed to have custom attribute names, but they have to be pre-pended with 'data-'. These custom attributes will not get submitted with your form, but they can be used to access your elements in Javascript. If you want to accomplish this, I would recommend creating a helper that generates options like this:
<option value="1" data-currecy-code="XXX">Andorra</option>
(2) Using values with custom splitting to submit additional data. If you actually want to submit the currency-code, I would recommend creating your select box like this:
= f.select :country_id, #countries.map{ |c| [c.name, "#{c.id}:#{c.currency_code}"] }
This should generate HTML that looks like this:
<option value="1:XXX">Andorra</option>
<option value="2:YYY">Argentina</option>
Which you can then parse in your controller:
#id, #currency_code = params[:country_id].split(':')

The extra attributes hash is only supported in Rails 3.
If you're on Rails 2.x, and want to override options_for_select
I basically just copied the Rails 3 code. You need to override these 3 methods:
def options_for_select(container, selected = nil)
return container if String === container
container = container.to_a if Hash === container
selected, disabled = extract_selected_and_disabled(selected)
options_for_select = container.inject([]) do |options, element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element)
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text.to_s)}</option>)
end
options_for_select.join("\n").html_safe
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
case
when Array === option
option = option.reject { |e| Hash === e }
[option.first, option.last]
when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
[option.first, option.last]
else
[option, option]
end
end
def option_html_attributes(element)
return "" unless Array === element
html_attributes = []
element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
end
html_attributes.join
end
Kinda messy but it's an option. I place this code in a helper module called RailsOverrides which I then include in ApplicationHelper. You can also do a plugin/gem if you prefer.
One gotcha is that to take advantage of these methods you must always invoke options_for_select directly. Shortcuts like
select("post", "person_id", Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] })
will yield the old results. Instead it should be:
select("post", "person_id", options_for_select(Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] }))
Again not a great solution, but it might be worth it to get to the ever so useful data-attribute.

I ran into this issue as well and created the "enhanced_select" Ruby Gem to solve this problem. You can find it here:
https://github.com/bkuhlmann/enhanced_select

Related

Create Arrays for multiple Languages

I am searching for an easy way to create a select with multiple languages. Currently, I have used a HELPER to store an array with my SELECT options.
Helper
list = ["Book", "DVD", "Table", "Chair"]
CATEGORY = Hash[*list.collect { |v| [v, list.index(v)] }.flatten]
View
<%= f.select :category, options_for_select(ApplicationHelper::CATEGORY, selected: 0), { :class => "selectpicker" } %>
If there is a way to somehow use the LOCALE file this would be amazing too.
One solution would be to keep translations in the DB, You can search there are multiple gems available like language select
If you want to pull options from your translation YML files, I suggest options_for_select. All in all something like:
en.yml
en:
my_options:
0: "Book"
1: "DVD"
2: "Table"
3: "Chair"
View:
<%= f.select :category, options_for_select(t("my_options").invert, selected: 0), { :class => "selectpicker" } %>
Rails i18n gives you a hash if you translate a non-leaf key, like "my_options". You need the invert because options_for_select expects the text before the value, and a translation hash is the other way around.
You can explore more about it there are many way to do it this one is one of them and simple way.

Multiple identical collection_select tags in a form in Rails

I have multiple identical collection selects inside a single form. I prefer this over a multiple select list for aesthetic and UX reasons. I have to use a terrible kludge to make everything work right, and I'm wondering if there is a more elegant way to do this:
From the view:
<% 3.times do |i| %>
<%= collection_select("selected_item_" + i.to_s.to_s, :name, #items, :name, :name, { :include_blank => true }, { id: "selected_item_" + i.to_s }) %>
<% end %>
From the controller:
ItemContainer = Struct.new(:name)
3.times do |i|
param = ('selected_item_' + i.to_s).to_sym
instance_variable = '#' + param_name
if params[param] && !params[param].empty?
#selected_items << params[param][:name]
instance_variable_set(instance_variable, ItemContainer.new(params[param][:name]))
end
end
#selected_channels.each.... # do what I need to with these selections
Most of these gymnastics are needed to ensure that the item is still selected if the page is refreshed. If there were some way to force collection select to use arrays, that would be the answer, but I couldn't make that work.
If I understang right you're looking for selegt_tag method (docs: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag)
You can write something like this
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
and it youd output two selects for people, which would be sent as an array on submit.
if you use the [] naming in your collection_select calls then the params will send over the data as an array
I am a bit confused as to the usage of collection_select here as it doesn't seem like you are using a model object? this example using select_tag - might be able to come up with something more appropriate to your issue if the model structures were known
# run this in the loop
# set selected_value to appropriate value if needed to pre-populate the form
<%= select_tag('name[]',
options_from_collection_for_select(#items, 'name', 'name', selected_value),
{ include_blank: true }
)
%>
in controller update/create action
# this works because the select tag 'name' is named with [] suffix
# but you have to ensure it is set to empty array if none are passed, usually only issue with checkboxes
names = params[:name] || []
names.each do |name|
puts name
end
side note: you can use string interpolation with ruby double quotes in places of + for string concatenation
<%= collection_select("selected_item_#{i}",
:name,
#items,
:name,
:name,
{ include_blank: true },
{ id: "selected_item_#{i}" }
)
%>
see also: http://apidock.com/rails/v3.2.13/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select

Simple - getting record id instead of desired attribute

This is a really simple question but I am new. I am trying to create a dropdown menu with values populated from the model. However, instead of displaying the city names, I am getting the record id's like: 0x007fee0b7442c0 (not sure if these are called id's, I think there is another term).
Controller:
#cities = City.find(:all, select: "name")
View:
<%= f.select(:city, #cities) %>
What am I doing wrong?
If you want just the name attribute from the database then do:
#cities = City.pluck(:name)
# => ["Sydney", "Melbourne", "Canberra"]
Try: select(object, method, choices, options = {}, html_options = {})
#cities = City.select(:name)
<%= f.select(:city, #cities.collect {|p| [ p.name, p.name ] }) %>
There is a guide to using collection_select here
http://www.fmhcc.com.au/ruby/rails/using-collection_select-in-rails/
You can also improve on #cities = City.find(:all, select: "name") by instead doing
#cities = City.pluck(:name)
If you want to display city name in the select box, and you want to pass the city id as a parameter on form submit, use this
f.select :city, #cities.map {|c| [ c.name, c.id ] }

How to populate a select_tag with an array of hashes?

In a Rails 3.2 app I'm trying to add a select field that takes its data from an external API call. This data is returned as an array of hashes:
[{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
How can I use this data to construct a select field that looks something like:
<select>
<option value="001"> NameA </option>
<option value="002"> NameB </option>
</select>
EDIT:
Thanks to the suggestions below I've tried the following:
A:
<%= select_tag 'model[field]', options_from_collection_for_select(#hash, :id, :name) %>
Gives an error:
undefined method `name' for {"name"=>"NameA", "id"=>"001"}:Hash
B:
<%= select_tag 'model[field]', options_from_collection_for_select(#hash) %>
Fixes the error but generates the wrong markup
<option value="{"name"=>"NameA", "id"=>"001"}"> {"name"=>"NameA", "id"=>"001"}</option>
So I think my problem is formatting the array of hashes correctly, and I don't know enough about manipulating arrays of hashes to work out how to do this.
Unless I'm looking in completly the worng direction, I think the key to this problem is to reformat the array at the top of this question to give:
{"NameA" =>"001", "NameB" =>"002"}
Is this even possible? And if so, how?
If you have array of hashes like this:
#people = [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
You can use options_for_select helper with collect method like this:
= select_tag "people", options_for_select(#people.collect {|p| [ p['name'], p['id'] ] })
And its done :-).
A better way to do it in only one command:
<%= select_tag "model[field]", options_for_select(#array_of_hashes.map { |obj| [obj['name'], obj['id']] }) %>
With your example hash:
irb> #array_of_hashes = [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
=> [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
irb> #array_of_hashes.map { |obj| [obj['name'], obj['id']] }
=> [["NameA", "001"], ["NameB", "002"]]
The easiest way to use Hashes in selects, for me is:
The hash:
REVISION_TYPES={"S"=>"Stock", "T"=>"Traducción"}
In the form:
select_tag(:revision_type,options_for_select(REVISION_TYPES.invert.to_a))
You can use options_for_select for this purpose. It takes a two dimensional Array. You can convert your hash like so:
data = [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
data_for_select = data.each { |hash| [hash['name'], hash['id']] }
options_for_select(data_for_select)
As a side note to options_from_collection_for_select, it is used in combination with objects. It iterates through the objects and sends a message for the label and one for the id.
Ok, I eventually figured out a solution that works, though it may not be the best.
$ #array_of_hashes = [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
=> [{"name"=>"NameA", "id"=>"001"}, {"name"=>"NameB", "id"=>"002"}]
$ #formatted_array_of_hashes = #array_of_hashes.each.map{ |h| { h["name"] => h["id"] }}
=> [{"NameA" => "001"}, {"NameB" => "002"}]
$ #merged_hash = Hash[*#formatted_array_of_hashes.map(&:to_a).flatten]
=> {"NameA" => "001", "NameB" => "002"}
I was then able to create a select field
<%= select_tag 'model[field]', options_for_select(#merged_hash) %>
that generates the correct markup
<select>
<option value="001">NameA</option>
<option value="002">NameB</option>
</select>
A little convoluted, but its working. I welcome any improvements.
I'm not really sure why, but none of the answers worked for me. maybe because of Rails 5, so I figured it out by myself and I'm happy to share.
It's now pretty but works well.
It's an Array of Hashes from an external API (Pipedrive) that I want to associate with the user's table:
This is in my form:
<%= form.collection_select(:id_user_pipedrive, Owner.all_id_name, :id, :name, :include_blank => '') %>
And this is in my fake model:
UserPipedrive = Struct.new(:id, :name)
def self.all_id_name
all["data"].map { |d| UserPipedrive.new(d["id"],d["name"]) }
end
Hope it helps someone.

Using select method in form_for in rails

I would like to use a method inside a form_for in rails to create a select tag with options where the value of the options come from one array and the options enclosed by the option tags come from another array.
For example, the first option would be:
<option value = Array1[0]> Array2[0] </option>
How do I do this? Can I use 'select' such as:
= form_for #activity do |f|
= f.select(Array1, Array2, {:selected => nil, :prompt => 'Select Stage'})
I couldn't get something like this working, even though this format seemed consistent with options_for_select as described in the rails API at api.rubyonrails.org.
Try this:
= f.select(:method, Array2.zip(Array1), { :selected => nil, :prompt => 'Select Stage' })
The zip method will combine two arrays to make one two-dimensional array.
So, for example, [1,2,3].zip([4,5,6]) will return [[1,4], [2,5], [3,6]].
select can then interpret this as a list of option texts and option values.
Given [['Male', 'm'], ['Female', 'f']], select will return
<option value="m">Male</option>
<option value="f">Female</option>

Resources