Multiple identical collection_select tags in a form in Rails - ruby-on-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

Related

Search through .select().group().distinct possible?

I want to create a search select that works on unique names through grouping...
The goal is to find duplicates to then use as a search parameter.
I want to find LineItem's :store_title's that match so I can create a select drop down for a way to search through LineItem's that match the specific :store_title.
Example:
LineItem DB:
line_item.title = "Hello"
line_item.title = "Hello"
line_item.title = "Okay"
line_item.title = "Bar"
I want to have a drop down select_tag in a search for the following:
[select]
"Hello"
"Okay"
"Bar"
And append all results that match LineItem.title of those which is selected.
I tried a few ways:
I so far have been able to get the unique drop down select field to "work" but not sure if it is the right way because it creates an array:
Controller:
#vendor_line_items = LineItem.where(vendor_id: #vendor.id).select(:store_title).group(:store_title).distinct
if params[:search]
#orders = Order.line_item_search(params[:search]).joins(:line_items).where(line_items: {vendor_id: #vendor.id})
end
ORders Model:
def self.line_item_search(search)
scope = joins(:line_items)
line_items = scope.where(line_items: { id: LineItem.where(store_title: "#{search.downcase}") })
line_items
end
View:
<%= form_tag vendor_orders_path, :method => 'get' do %>
<%= collection_select(:search, params[:search], #vendor_line_items, :store_title, :store_title, {}, {class: "form-control-sm col-5"})%>
<%= submit_tag "Search", :name => nil, class: "btn btn-primary btn-sm" %>
<% end %>
Error:
Undefined method 'downcase' for ["store_title"]:Array:
Can I alter my model to allow the array, or should i be finding the unique store titles another way?
If you just want to downcase the strings in that array, you could do something like this:
line_items = scope.where(line_items: { id: LineItem.where(store_title: search.map(&:downcase)) })
That will provide a list of downcased strings to your query.
I tried finding a way to remove the brackets before searching so the brackets were not present in the search parameters but couldn't figure it out.
Doing...:
line_items = scope.where(line_items: { id: LineItem.where(store_title: "#{search.join(', ')}") })
Solved it. This removes the brackets before searching the DB and works.
**
UPDATE:
**
View for response and answer to my issue:
Rails search form through arrays doesn't work on second search attempt

print two values in collection_select (Rails Forms)

For my form, I have this:
<%= tag_field.collection_select( :id, Material.order(:name), :id, :name,
:prompt => "-select-")%>
This prints my materials names.
example:
Cat
Cat
However, this is not helpful because the materials have the same names.
There is another attribute in the Material record, :color.
I want it to print out this in the dropdown
Cat - Brown
Cat - Orange
How do I go about doing this? I tried calling a method instead, but it doesn't print out the way I want it to. Here's what I did.
View:
<%= tag_field.collection_select( :id, Material.order(:name), :id, :something,
:prompt => "-select-")%>
Model:
def something
materials_array = []
Material.all.each do |material|
if material.color == nil
material.name + '-' + material.size
else
materials_array.push(material.name + '-' + material.color)
end
end
materials_array
end
However, the dropdown prints out like this:
["Cat - Brown", "Cat - Orange"]
["Cat - Brown", "Cat - Orange"]
It prints out twice, with the same values. I think I'm close? Please help.
I think it's easier if you use select instead of collection_select. Try it:
<%= tag_field.select :id, Material.order(:name).map{ |m| [ "#{m.name} - #{m. color}", m.id ] }, {prompt: "-select-"} %>
This answer explains clearly the usage of collection_select helper. The method :name_with_initial (which corresponds to the method something in your code) is explained as:
:name_with_initial, # this is name of method that will be called for
# every row, result will be set as value
# as a result, every option will be generated by the following rule:
# <option value=#{author.id}>#{author.name_with_initial}</option>
# 'author' is an element in the collection or array
So if you are getting results twice it means the collection/array has redundant values.

Rails how to call helpers such as .humanize on an attribute of collection_select

I have this collection_select <%= collection_select(:template, :template_id, #templates, :id, :name) %> and I want to basically call :name.humanize, but obviously that doesn't work.
How can i call a method such as .humanize on an attribute of collection_select (a hash attribute?)
EDIT
Here's a snippet from my controller:
#templates = Template.select(:name).group(:name)
p "templates = #{#templates}"
Here's what appears in the console:
"templates = #<ActiveRecord::Relation:0x007f84451b7af8>"
With Rails 4
Just like this:
<%= collection_select(:template, :template_id, #templates, :id, Proc.new {|v| v.name.humanize}) %>
In the Proc block, v will be your models, iterated through a each loop.
collection_select works with anything that inherit from Enumerable and have no limit in the content of the Proc.
With Rails 3
You must do it your self, by preparing your data before passing it to collection_select
# This will generate an Array of Arrays
values = #templates.map{|t| [t.id, t.name.humanize]}
# [ [record1.id, record1.name.humanize], [record2.id, record2.name.humanize], ... ]
# This will use the Array previously generated, and loop in it:
# - send :first to get the value (it is the first item of each Array inside the Array)
# - send :last to get the text (it is the last item of each Array inside the Array)
# ( we could have use :second also )
<%= collection_select(:template, :template_id, values, :first, :last) %>

Array as Parameter from Rails Select Helper

I'm working on a legacy project that is using acts_as_taggable_on which expects tags to come in arrays. I have a select box allowing users to select a tag on a Course in a field called categories. The only way mass assignment create will work is if params looks like this params = {:course => {:categories => ['Presentation']}}. I've currently a view with this helper:
<%= f.select 'categories', ['Presentation' , 'Round Table' , 'Demo', 'Hands-on'] %>
Which will give me a parameter like params = {:course => {:categories => 'Presentation'}}. This doesn't work since Acts as tag gable apparently can't handle being passed anything other than a collection.
I've tried changing categories to categories[] but then I get this error:
undefined method `categories[]' for #<Course:0x007f9d95c5b810>
Does anyone know the correct way to format my select tag to return an array to the controller? I'm using Rails 3.2.3
I didn't work with acts_as_taggable_on, but maybe this simple hack will be suitable for you? You should put it before mass-assignment.
category = params[:course][:categories]
params[:course][:categories] = [category]
If you are only going to allow the selection of ONE tag, you could do:
<%= f.select 'categories', [['Presentation'] , ['Round Table'] , ['Demo'], ['Hands-on']] %>
Each one item array will have first for the display value, and last for the return value, which in this case will both return the same thing, as the first element of the array is the same as the last element when the array as one element.
Seems like select doesn't give you that option.
If I understand correctly, one option might be to use a select_tag instead and just be explicit about where you want the selection in the params:
<%= select_tag 'course[categories][]', options_for_select(['Presentation' , 'Round Table' , 'Demo', 'Hands-on']) %>
That ought to get your params the way you need them.
Here's what I'm using for one of my projects:
<% options = { include_blank: true } %>
<% html_options = { required: true, name: "#{f.object_name}[#{resource.id}][days][]" } %>
<%= f.select :days, DAYS, options, html_options %>
Without html_options[:name], Rails handles the name of the select tag and spits out something like
service[service_add_ons_attributes][11][days]
but I need
service[service_add_ons_attributes][11][days][]
So I override it.
Hope that helps.

ruby on rails f.select options with custom attributes

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

Resources