Rails form helper select when adding custom css class - ruby-on-rails

This question comes up all the time, which probably points to a design flaw (#dhh)
WHy does this work:
<%= form.select :category_id, category_select_collection, {}, html_options = { class: "form-select"} %>
but this does not work:
<%= form.select :category_id, category_select_collection, html_options = { class: "form-select"} %>
I bet here's some subtlety about parameter ordering, named parameters, defaults and so on that I am tripping on. But I don't see where my understanding is incorrect.

You have a parameter ordering problem. form.select looks like this:
select(method, choices = nil, options = {}, html_options = {}, &block)
Wraps ActionView::Helpers::FormOptionsHelper#select for form builders:
The third argument are the options, the fourth are the HTML options so you want your { class: ... } in the fourth argument.
You might also be misinterpreting what html_options = { class: "form-select"} means. That's an assignment to the local variable html_options inside the arguments to the select method. An assignment is an expression that evaluates to the RHS so it still works in the first case but you could say:
<%= form.select :category_id, category_select_collection, {}, any_local_variable_name = { class: "form-select"} %>
and achieve the same result (assuming of course that you're not using the html_options variable later on).

Related

How to pass two attributes as the text_method to a collection_select in rails

I have a collection_select in a rails form that looks like this:
<%= form.collection_select :post_id, Post.all, :id, :title, {}, { class: "mt-1 block" } %>
What I can't seem to figure out from the docs or googling, is how to pass multiple attributes from the Post to the dropdown so the user sees more than just the :title. Something like this:
<%= form.collection_select :post_id, Post.all, :id, :title + :category, {}, { class: "mt-1 block" } %>
I can create a custom method to pass to text_method like :title_with_category in the Post model like:
<%= form.collection_select :post_id, Post.all, :id, :title_with_category, {}, { class: "mt-1 block" } %>
Post.rb:
def title_with_category
self.title + " " + self.category
end
But is this the best way to do this? If so, what is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?
Firstly, it's safer to do this in case one of the items is ever nil:
Post.rb
def title_with_category
"#{title} #{category}"
end
Next your selection. In the controller, return the options as an attribute:
def new
#post_options = Post.all.collect{|post| [post.id, post.title_and_category]}
# OR
#post_options = Post.all.collect{|post| [post.id, "#{post.title} #{post.category}"]}
# you can skip the model method with the second option
end
And on the form:
<%= form.select :post_id, #post_options, {}, { class: "mt-1 block" } %>
See form select.
You can pass a callable to collection_select for both the value_method and text_method arguments:
<%= form.collection_select :post_id,
Post.all,
:id, # value_method
->(p){ "#{p.title} #{p.category}" }, # text_method
{},
{ class: "mt-1 block" }
%>
A callable is any object that responds to the call method such as lamdba and proc objects.
It is called with the post for each iteration of the loop.
What is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?
There is no clear cut answer if you choose to extract this out into a separate method. The model would be the simplest solution but you can also argue that presentational logic should be separated from buisness logic and that models already have tons of responsiblities.
I think we can all agree on that ApplicationHelper is the least suitible option unless you to just are aiming to toss your code into a junk drawer.
This code could go into Post, PostHelper, PostPresenter (if you're into the decorator pattern) or a custom form builder (which seems slightly overkill).

Multiple attribute in collection_select in Rails

I am trying to allow for multiple value selection from a collection in a Rails form. The field is working but does not allow for multiple selections (once an alternative option is selected the previously selected is unselected). I am using Bootstrap CDN, which I don't presume is causing issues, but I may be wrong?
Can you see anything wrong with this code?
<div class="field form-group row">
<%= f.label :industry_ids, class:"col-sm-3"%>
<%= f.collection_select(:industry_ids, Industry.all, :id, :name, {:multiple => true}, size: Industry.all.length) %>
</div>
Thanks for your help.
I believe your problem is that you're putting {:multiple => true} in the wrong options hash. The method signature for collection_select looks like this:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
multiple is an html attribute of the select tag itself (here's a doc), so you want to pass that to html_options, not options. Your code looks like this:
f.collection_select(:industry_ids, Industry.all, :id, :name, {:multiple => true}, size: Industry.all.length)
Where Industry.all is the collection, :id is the value_method, and :name is the text_method, which means { :multiple => true } is getting passed to options, not html_options. Move it to the second hash and you should be fine.

How to add class to Rails select form helper when using as a block

The documentation for Rails select form helper states (see documentation):
select(object, method, choices = nil, options = {}, html_options = {}, &block)
Which allows adding a class simple, like so:
<%= f.select :some_attr, MYOPTIONS, {}, {class: 'my-class'} %>
My question is, how do I add a class to it when using it as a block? Rails documentation states:
select(report, "campaign_ids") do
available_campaigns.each do |c|
content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
end
end
It doesn't work when I use it like so:
<%= f.select :some_attr, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
Nor does it work if I use:
f.select :some_attr, class: 'my-class' do
The class is not applied to the select tag in the HTML.
I solved my own problem, although I don't fully understand the answer, so if someone else understands this better, I'd love to hear your answer.
To get it to work, I simply added an additional empty hash to the beginning, like so:
<%= f.select :some_attr, {}, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
The second hash is still options and the last is still html_options, so as an example, you can also add include_blank like so:
f.select :some_attr, {}, {include_blank: true}, {class: 'my-class'}
However, I don't know what the first hash is, nor what values can be passed there. I've looked at the Rails source, but I still have no clue. If you have insight into this, I'd love to hear it.
A couple oddities to be aware of:
In your example, you're using f.select, which you can find a reference for here:
https://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/select
Only the first parameters is required, the rest have defaults. However, to assign that HMTL class, you had to have a value for the fourth parameter, which necessitated having something for the second and third parameters as well.
What you ended up with is a valid solution:
<%= f.select :some_attr, {}, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
The block, when provided, takes precedence over the literal value (an empty hash in this case).
Surprisingly, if you were rendering this tag using select_tag instead of f.select, passing a block wouldn't be an option:
https://apidock.com/rails/ActionView/Helpers/FormTagHelper/select_tag

Select ActionView::FormHelper html_options not being set

I currently have the following in my application and I am simply trying to apply a class to the select as shown below
<%= f.select :widget_id, options_from_collection_for_select(#widgets, "id", "name"), html_options: {class: 'form-control'} %>
According to the select helper on API dock it shows it should be set up as followed:
select(object, method, choices, options = {}, html_options = {}) public
My problem is that for some reason when I inspect the element I see:
<select id="user_widget_id" name="user[widget_id]">
I don't understand why the class is not being included.
Try this:
<%= f.select :widget_id, options_from_collection_for_select(#widgets, "id", "name"), {}, {class: 'form-control'} %>
The third parameters is for options, you want to pass your html_options in the 4th parameters, and no need to actually declare "html_options" too. Hope it helps !
Official doc: select(object, method, choices, options = {}, html_options = {})

Can you pass a class into a Rails datetime select helper?

Trying to format a Datetime select helper by passing in a class like so has not been working. I at first thought Bootstrap might be wrong, but after looking at the source, 'span2' is not there.
<%= f.datetime_select :arrival_nor_tendered, class: "span2" %>
This seems like the headslap way to do it but I may be wrong.
Try this:
<%= f.datetime_select :arrival_nor_tendered, {}, { class: "span2" } %>
The last parameter is html_options but it has a hash parameter before it and ruby may missinterpret the two.

Resources