print two values in collection_select (Rails Forms) - ruby-on-rails

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.

Related

rails collection_select should print my collection

I got an error while trying to use collection_select ;)
I got this code in my view:
<%= f.collection_select(:channel, :channel_id, #channels, :id, :channelname, prompt: true) %>
in my controller I have this:
#channels = Channel.all
and I got this error:
undefined method `merge' for :channelname:Symbol
Whats my failure ?
Thanks at all!
According to the docs:
collection_select(method, collection, value_method, text_method,
options = {}, html_options = {}) public
So therefore you should use:
<%= f.collection_select(:channel_id, Channel.all, :id, :channelname, prompt: true) %>
You can use like
Channel.all.pluck(:id, :channelname)
For example take look at below
collection_select(
:post, # field namespace
:author_id, # field name
# result of these two params will be: <select name="post[author_id]">...
# then you should specify some collection or array of rows.
# It can be Author.where(..).order(..) or something like that.
# In your example it is:
Author.all,
# then you should specify methods for generating options
:id, # this is name of method that will be called for every row, result will be set as key
: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
:prompt => true # then you can specify some params. You can find them in the docs.
)
try this
<%= f.collection_select(:channel_id, :id, prompt: true) %>

rails form for model select box with 1-dimensional data

I want to limit the entry possibilities for a text field in my model to a previously defined array.
How do I make an options_for_select with just a 1-dimensional array like ["foo","bar","foobar"]?
I tried
form_for #mappings do |f|
f.select(:mapping_type, options_for_select(["foo","bar","foobar"]), class: "..."
end
But the select box comes out all messed up like this:
<select name="section_mapping[mapping_type]" id="section_mapping_mapping_type">
as opposed to what it should be:
<select name="mapping_type" >
EDIT:
I changed the f.select to select_tag and the form shows up without any errors but when I submit it, it leaves that field empty
EDIT 2:
f.collection_select(:mapping_type, options_for_select([...]), class: "..."
works as in it submits the form with the value correctly, but the HTML class is not applied. Why is that?
Basically, you want to be able to tie your collection select to a property of the object (in your case, #mappings)
Also, from the doc on rails collection_select, it will take options as follow:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) public
Objet: the object you are binding the selected option to (#mappings [f]) in this case
method: The property/attribute of the object (in this case, mapping_type)
collection: The collection for select ( ["foo","bar","foobar"] )
value_method: The value you want to send back with the submit (Note that this is a method which means you should be able to call it on an object.) more on this later.
text_method: The value you want to show as text on the select option on the view (this is also a method as above, more on this later as well)
options: any additional option you want, (e.g: include_blank)
html_options: eg: id, class etc.
As concerning the value_method and text_method, these are methods that should be called on your collection, which means that your collection will be an array of objects.
To this end, you can have the following:
class CollectionArr
include ActiveModel::Model
attr_accessor :name
ARR = [
{"name" => "foo"},
{"name" => "bar"},
{"name" => "foobar"}
]
def self.get_collection
ARR.collect do |hash|
self.new(
name: hash['name']
)
end
end
end
From here, calling CollectionArr.get_collection will return an array of objects where you can cal .name to return either foo, bar, or foobar. This makes using the collection_select and easy deal from here:
<%= f.collection_select : mapping_type, CollectionArr.get_collection, :name, :name, {:include_blank => "Select one"} %>
And all is green...

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) %>

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

String concatenation not possible?

So over the last 2 hours, I've been trying to fill a combobox with all my users. I managed to get all firstnames in a combobox, but I want their full name in the combobox. No problem you would think, just concatenate the names and you're done. + and << should be the concatenation operator to do this.So this is my code:
<%= collection_select(:user, :user_id, #users, :user_id, :user_firstname + :user_lastname, {:prompt => false}) %>
But it seems RoR doesn't accept this:
undefined method `+' for :user_firstname:Symbol
What am I doing wrong?
What you need to do is define a method on the User model that does this concatenation for you. Symbols can't be concatenated. So to your user model, add this function:
def name
"#{self.first_name} #{self.last_name}"
end
then change the code in the view to this:
<%= collection_select(:user, :user_id, #users, :user_id, :name, {:prompt => false}) %>
Should do the trick.
This isn't really rails giving you an error, it's ruby. You're trying to combine the symbols :user_firstname and :user_lastname
A symbol is a variable type, just like integer, string, or datetime (Well technically they're classes, but in this context we can think of them as variable types). They look similar to strings, and can function similarly to them, but there is no definition for the behavior of symbol concatenation. Essentially you're trying to send the method user_firstnameuser_lastname which is just as non-sensical as trying to concat two Symbols.
What you need to understand is that this parameter is looking for a method on your User object, and it won't understand the combination of two symbols. You need to define a method in your model:
def fullname
[user_firstname, user_lastname].reject{|v| v.blank?}.join(" ")
end
This'll return your first + last name for you, and then in the parameter you should send :fullname (because that's the method it'll call on each user object in the collection):
<%= collection_select(:user, :user_id, #users, :user_id, :fullname, {:prompt => false})%>
Also, it's considered poor practice to prefix every single column with the table name. user.user_firstname just looks redundant. I prefer to drop that prefix, but I guess it's mostly up to personal preference.
The arguments for value and display attribute are method names, not expressions on a user object.
To control the format more precisely, you can use the select tag helper instead:
select("user", "user_id", #users.each {|u| [ "#{u.first_name u.last_name}", u.user_id ] })
The docs are pretty useful.

Resources