Custom data attribute for grouped_collection_select - ruby-on-rails

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

Related

populating a custom time field on ruby on rails with simple_form

I'm a beginner to ruby and ruby on rails, and I'm attempting to create a complex form using simple_form. Everything is working already, but I wanted to customize the "time" field in a very specific way.
When I use:
<%= f.input :hour %>
It renders two select fields, being the "hour" field populated with options from 00 to 23, and the "minute" field populated with options from 00 to 59.
But that's not what I want. I want to replace the use of two select fields for the time with a single select field with custom pre-populated options, with time ranging from 8am until 22pm, in 15 minutes increments, and also have the text for the options show times with the syntax "8h00", "8h15", "8h30", "8h45", "9h00", "18h00", "18h15", "18h30", "18h45", etc.. being "21h45" the last option available.
I have managed to customize it a bit more using these options:
<%= f.input :hour, :as => :time, :minute_step => 15 %>
Although that solves the 15 minute increment problem, it still renders two select fields.
This would be the html equivalent of what I wanted to have simple_form render for me:
<select name="hour">
<option value="08:00:00">08h00</option>
<option value="08:15:00">08h15</option>
<option value="08:30:00">08h30</option>
<option value="08:45:00">08h45</option>
...
<option value="21:30:00">21h30</option>
<option value="21:45:00">21h45</option>
</select>
I'm pretty sure this is very easy to implement using a collection that loops from 8..21 and then loops again with '00', '15', '30' and '45', and then outputs options in the syntax that I want (hour + "h" + minute).
I want to create a custom "date" field, because there will many date fields in a single form (for a "appointment" related application that I'm creating), but since I'm a beginner on ruby on rails, I'm really lost on what would be the most smart way to implement this. I'm not sure if I should implement a custom field in simple_form, if I should use a helper function, or what.
With help from Carlos Antonio da Silva, from simpleform's mailing list, we've fixed this by creating a custom input like this:
app/inputs/hour_input.rb
class HourInput < SimpleForm::Inputs::Base
def input
#builder.select(attribute_name, hour_options, { :selected => selected_value }, { :class => "input-medium" })
end
private
# The "Selecione..." string could also be translated with something like: I18n.t("helpers.select.prompt')
def hour_options
hour = [['Selecione...', '00:00:00']]
(8..21).each do |h|
%w(00 15 30 45).each do |m|
hour.push ["#{h}h#{m}", "#{"%02d" % h}:#{m}:00"]
end
end
hour
end
def selected_value
value = object.send(attribute_name)
value && value.strftime("%H:%M:%S")
end
end
and then <%= f.input :hora, :as => :hour %> in the view.
With suggestions from simpleform's google mailing list, this is how I fixed my own problem:
in the view file, I created a input like this:
<%= f.input :hour, collection: options_for_time_select, selected: f.object.hour.strftime("%H:%M:%S") %>
And in application_helper.rb file I created a function this way:
module ApplicationHelper
def options_for_time_select
hour = Array.new
for $h in 8..21 do
for $m in ['00', '15', '30', '45'] do
hour.push [$h.to_s + "h" + $m.to_s, "%02d" % $h + ":" + $m + ":00"]
end
end
hour
end
end
I have no idea if this is the most elegant way to solve this problem, or if it really works on every scenario. I would love to get corrections or a better solution, if possible.

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

rails collection_select vs. select

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>

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

Resources