rails collection_select vs. select - ruby-on-rails

collection_select and select Rails helpers: Which one should I use?
I can't see a difference in both ways. Both helpers take a collection and generates options tags inside a select tag. Is there a scenario where collection_select is better than select? or is anything I am missing here?

collection_select is intended to be used when the list of items is an array of ActiveRecord objects. collection_select is built on the top of select so it's a convenient method when you need to display a collection of objects and not an array of strings.
collection_select(:post, :author_id, Author.find(:all), :id, :name)

I have written something on that a while back, have a look at
http://nasir.wordpress.com/2007/11/02/not-binding-your-selection-list-to-a-particular-model-in-rails/
Hope that helps

And regarding select, you can use it with a Hash. I used to use it with ENUM.
# In a hypothetical Fruit model
enum types: { 'Banana' => 0, 'Grape' => 1, 'Mango' => 2 }
# In the view
f.select :type, Fruits.types.invert
Note that I had to use invert in order to show the correct value in option:
<select>
<option value="0">Banana</option>
<option value="1">Grape<option>
<option value="2">Mango</option>
</select>
To refer to it in a show file you can use Fruit.types and this will return our previous Hash. This way you can do:
Fruit.types[obj.type]
Last note: You can use symbols instead numbers if you prefer enum types: { 'Banana' => :banana, ...and you will get <option value="banana">Banana</option>

Related

Custom data attribute for grouped_collection_select

grouped_collection_select accepts nine parameters, including an options attribute. How do you add a custom attribute in options, a data tag, to be used when an option is selected?
The select is coded as of now as:
= f.grouped_collection_select :course_id, Discipline.order(:name), :courses, :name, :id, :display
I want to add a custom data attribute so that the output is like:
<select id="enrollment_course_id" name="enrollment[course_id]">
<option selected="selected" value="7" data-duration=8>Introduction to Guitar (8 weeks)</option>
<option value="8" data-duration=8>Strings 1-3 (8 weeks)</option>
<option value="9" data-duration=10>Strings 4-6 (10 weeks)</option>
<option value="10" data-duration=12>Basic Chords (12 weeks)</option>
</select>
How do we generate the custom attribute programmatically?
There seems to be no nice and properly documented way to do this.
Solution 1. use non fully documented features of grouped_collection_select:
f.grouped_collection_select :course_id, Discipline.order(:name), :courses_with_duration_data, :name, ->(el){el.first.id}, ->(el){el.first.display}
Where you need to implement courses_with_duration_data like this:
class Discipline
has_many :courses
def courses_with_duration_data
courses.map {|course| [course,{data: {duration: course.duration}}]}
end
end
It relies on that if you pass array of arrays to options_for_select the hash within the arrays will be treated as html options.
It has a disadvantage of putting view related methods to your model. And it something that might break with future rails releases as this use is not documented on grouped_collection_select, so you're probably better of generating the options yourself.
Solution 2. Reimplement options yourself:
Reimplement custom option_groups_from_collection_for_select in a helper. This could look something like this:
def options_for_disciple_with_courses(disciplines)
disciplines.map do |discipline|
option_tags = options_for_select(
discipline.courses.map {|course| [course.display, course.id, {data: {duration: course.duration}}]}
)
content_tag(:optgroup, option_tags, label: discipline.name)
end.join.html_safe
end
And your view would became:
f.select :course_id, options_for_disciple_with_courses(Discipline.order(:name))

Can someone explain collection_select to me in clear, simple terms?

I am going through the Rails API docs for collection_select and they are god-awful.
The heading is this:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
And this is the only sample code they give:
collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
Can someone explain, using a simple association (say a User has_many Plans, and a Plan belongs to a User), what I want to use in the syntax and why?
Edit 1: Also, it would be awesome if you explained how it works inside a form_helper or a regular form. Imagine you are explaining this to a web developer that understands web development, but is 'relatively new' to Rails. How would you explain it?
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.
)
Or your example can be represented as the following code:
<select name="post[author_id]">
<% Author.all.each do |author| %>
<option value="<%= author.id %>"><%= author.name_with_initial %></option>
<% end %>
</select>
This isn't documented in the FormBuilder, but in the FormOptionsHelper
I've spent quite some time on the permutations of the select tags myself.
collection_select builds a select tag from a collection of objects. Keeping this in mind,
object : Name of the object. This is used to generate the name of the tag, and is used to generate the selected value. This can be an actual object, or a symbol - in the latter case, the instance variable of that name is looked for in the binding of the ActionController (that is, :post looks for an instance var called #post in your controller.)
method : Name of the method. This is used to generate the name of the tag.. In other words, the attribute of the object you are trying to get from the select
collection : The collection of objects
value_method : For each object in the collection, this method is used for value
text_method : For each object in the collection, this method is used for display text
Optional Parameters:
options : Options that you can pass. These are documented here, under the heading Options.
html_options : Whatever is passed here, is simply added to the generated html tag. If you want to supply a class, id, or any other attribute, it goes here.
Your association could be written as:
collection_select(:user, :plan_ids, Plan.all, :id, :name, {:prompt => true, :multiple=>true })
With regards to using form_for, again in very simple terms, for all tags that come within the form_for, eg. f.text_field, you dont need to supply the first (object) parameter. This is taken from the form_for syntax.

Rails FormTagHelper missing important select and collection_select methods

You can do this when you use form_for(#model...):
collection_select(:subscription, :duration, ["Some", "Values"], :to_s, :to_s, {:prompt => true})
And the output is something like this:
<select id="subscription_duration" name="subscription[duration]">
<option value="">Please select</option>
<option value="Some">Some</option>
<option value="Values">Values</option>
</select>
If you use a form without a model, you don't have that nice helper method to create the <option> tags for you. Instead, you have to do this:
select_tag("subscription", '<option value="Some">Some</option><option value="Values">Values</option>')
FormHelper and FormOptionsHelper work together on a form wrapping a model, and they have the select and collection_select to make life easy. For a plain form_tag (without a model), however, there is no such FormOptionsTagHelper. FormTagHelper has a select_tag method, but you have to manually write out the options which is a waste. It seems like this has been fixed somewhere.
I could write my own helper to get rid of writing those option tags manually, but thats what FormOptionsHelper#collection_select does... Is there an equivalent out there for forms without models?
select and collection_select can be called without a model. I usually use a combination of two significant words, and an array of pairs [label, value] to select. The only drawback is having to use the format abc[xyz].
You tried to use options_for_select?
select_tag 'Account', options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")

Using Multiple collection_select elements on a form with multiple models

I'm just learning rails and I've run into a bit of a snag. Let me start with a simple breakdown of my application - it's a cookbook (of sorts)
Recipes have one or more ingredients (tuna, spleens, etc)
Ingredients have one unit (ounces, pounds, etc)
Units are pulled from a lookup table
Here's a screenshot to help clarify things further:
Form Mockup
Here's my issue: my collection_select elements names should be something like unit[id][]
Instead, they're all just named unit[id]. Here's the snippet I'm using:
collection_select(
:unit,
:id,
#units,
:id,
:name,
options = {
:prompt => "Please Select",
:class => "ingredient_unit",
:name => "unit[][]",
:id => "unit:" + i.to_s()
}
);
However, this is what it is outputting:
<select id="unit_id" name="unit[id]">
<option value="">Please Select</option>
<option value="1">Ounces</option>
</select>
...
Now, in php, these dropdowns would be named unit[]. Am I going about this the wrong way?
Thanks for the help
I am not sure what the "name" option does in the "options" hash. Can you post a link to where you found documentation of that? It looks like you are using the collection select helper properly. What do you mean by "these drop downs would be named unit[]"? It might help if you tell us your end goal of this form as Rails usually just handles stuff for you. Take advantage of its magic.
Also if you are a Rails beginner, highly recommend checking out Ryan Bates' screencasts on complex forms. Here is the link to part 1:
http://railscasts.com/episodes/73-complex-forms-part-1

select_tag is sorting (strangely) [Rails]

I have a select box that looks like this (within a form_for)
<%=f.select(:whatever_id, {"blah"=>0, "blah2"=>1, "blah3"=>2, "blah4"=>3}, {:include_blank => true}) %>
and the output is good, but weird... like this:
<select id="personal_information_whatever_id" name="personal_information[whatever_id]"><option value=""></option>
<option value="1">blah2</option>
<option value="2">blah3</option>
<option value="0">blah</option>
<option value="3">blah4</option></select>
But I want it to go in order... what is happening, and how can I correct it?
Edit: I feel like the answer has to do with this
You can never be guaranteed of any
order with hashes. You could try
.sort() to sort the values in
alphabetical order.
is there anything I can use aside from a hash?
Yes, you should use an array of arrays. The easiest way with your example would be something like this:
<%=f.select(:whatever_id, [["blah", 0], ["blah2", 1], ["blah3", 2], ["blah4", 3]], {:include_blank => true}) %>
This should suffice. Take a look at the docs at api.rubyonrails.com too.
In your model, define your hash (in your case your model is "whatever" and your hash "blahs"):
BLAHS = { "blah"=>0, "blah2"=>1, "blah3"=>2 }
In the select tag where you put your hash, type
Whatever::BLAHS.sort {|a,b| a[1] <=> b[1]}
This creates an array as described in other answers, ordered by the second item (the id/numbers).
After saving, when you pull a Whatever back out of the database and want to show the field blah, do this
Whatever::BLAHS.index whatever.blah
The array of arrays mentioned by others works, but when you want to show a Whatever, how do you show the value of blah in a nice way? I recommend sticking with the hash as it solves this problem.
The problem is that the options parameter is a hash, and hashes have no guaranteed order.
This should work for you
<%= f.select(:whatever_id, ([["blah",0],["blah2",1],["blah3",2]]), {:include_blank => true}) %>
Edit in response to your comment: For collections see collection_select

Resources