Rails 4 -Simple Form how to save key and display value - ruby-on-rails

I'm trying to make an app in Rails 4. I use simple form for forms.
I have an attribute called 'self_governance' in my model.
I've written a helper method so that I can define 5 levels which can be stored for this attribute, but which are displayed to the user as options (rather than numbers 1 .. 5).
In my helper, I have:
module PreferencesHelper
def self_gov_selector
[
[ 'tier 1','1'],
[ 'tier 2','2'],
[ 'tier 3','3'],
[ 'tier 4','4'],
[ 'tier 5','5'],
]
end
In my form, I then have:
<%= f.input :self_governance, :label => "Select your tier", collection: self_gov_selector %>
Then in my show, I'm trying to figure out how to display 'tier 1' instead of '1'.
I have tried:
<%= #preference.self_governance %>
<%= #preference.self_gov_selector %>
I can't find anything that works. They all display 1 instead of Tier 1 in the views.
How can I make this work?

The params posted by the form will only include the second value in the array, so you're likely storing your value as an integer in your database table.
A simple solution is to use an enum to map the integers you're storing to the values they represent:
In your Preference model:
enum self_governance: {
tier_1: 1,
tier_2: 2,
tier_3: 3,
tier_4: 4,
tier_5: 5
}
Then update your view accordingly:
<%= #preference.self_governance.try(:humanize) %>
EDIT:
An additional bonus of this approach is that you can replace your helper method with calling the enum directly:
f.input :self_governance, as: :select, label: "your label", collection: Preference.self_governances.map { |key, val| [key.humanize, val] }

Related

Rails 4 - how to use enum?

I'm trying to make an app on Rails 4.
I posted this question and got some advice: Rails 4 -Simple Form how to save key and display value
I am trying to figure out how to implement this advice.
At the moment, I have a preference model with:
enum self_governance: {
tier_1: 1,
tier_2: 2,
tier_3: 3,
tier_4: 4,
tier_5: 5
}
enum autonomy: {
tier_11: 1,
tier_21: 2,
tier_31: 3,
tier_41: 4,
tier_51: 5
}
In my preference form, I have:
<%= f.input :self_governance, as: :select, label: "Select your governance approach", collection: Preference.self_governance.to_a.map { |p| [p.humanize, p] } %>
I have a preferences show view:
<%= #organisation.preference.self_governance.try(:humanize) %>
When I save all this and try it, I get this error:
NoMethodError at /preferences/1/edit
undefined method `self_governance' for #<Class:0x007fde5b9fb500>
Did you mean? self_governances
Can anyone see how to make this work?
Do I maybe need to add def/end tags to the enum in the preference model? I don't have any experience with using the code 'enum'
You're so close :) The fix is right in the error.
Your select is calling
Preference.self_governance.to_a.map { |p| [p.humanize, p] }
And your error tells you the pluralization is wrong. Remember that if you call enum on a single object, it will be
#preference.self_governance
But if you call on the model itself, Preference, and request a collection it's plural.
Preference.self_governances
Because enum is special, uour enum's could just be arrays, instead of hashes:
enum self_governance: [ tier_1, tier_2, tier_3, tier_4, tier_55 ]
enum autonomy: [ tier_11, tier_21, tier_31, tier_41, tier_51 ]
Your view would look like:
<%= f.input :self_governance, as: :select, label: "Select your governance approach", collection: Preference.self_governances.map { |key, value| [key.humanize, key] } %>
It will store the index number of the array, like magic :)
Rails pluralizes the enum collection for you, so yours could be self_governances, for instance.
What that means is that Preference.self_governances would return the hash with the definitions and the attribute that actually holds the value is the one in singular like #preference.self_governance
An example:
#preference = Preference.new
#preference.self_governance = Preference.self_governances[:tier_1]
When you use enum what rails will do internally is add a pluralized class method definition with the name that you defined that will return a hash with the values and, will use the name that you defined, as it was written by you, for an attribute accessor that will either get/set the actual value of the enumeration on instances of your object.
Another, common use is for status, given a Test class:
enum status: {
active: 1,
inactive: 2
}
So for the above sample rails would add a Test.statuses methods that simply returns the values of your enum. Then, for an instance of a Test object you would have an accessor #instance.status with the name of your name which you can use to get or set a status from the hash returned by Test.statuses
Hopefully it makes sense.

Rails 4 - key value pair

I'm trying to make an app in Rails 4.
I've recently asked these 2 questions, and taken the advice in the responses. Rails 4 - how to use enum?
I'm still struggling.
I have a form with an input selector:
<%= f.input :self_governance, as: :select, label: "Select your governance approach", collection: Preference.self_governances.to_a.map { |p| [p.to_s.humanize, p] } %>
When I save this and try it, the select menu shows:
["tier_1", 1]
What I want is to display: Tier 1
At the moment, I have a preference model with:
enum self_governance: {
tier_1: 1,
tier_2: 2,
tier_3: 3,
tier_4: 4,
tier_5: 5
}
enum autonomy: {
tier_11: 1,
tier_21: 2,
tier_31: 3,
tier_41: 4,
tier_51: 5
}
I have a preferences show view:
<%= #organisation.preference.self_governance.try(:humanize) %>
Also, when I accept the form problem (for now) and try to render the show page, I get this error:
'["tier_1", 1]' is not a valid self_governance
Can anyone see what I've done wrong?
I just want to save the number 1 in the database, but display the words 'Tier 1'.
Update your form to properly return a collection of keys and values from your enum. Preference.self_governances is a type of hash object.r Rather than call to_a, just iterate over the keys and values:
<%= f.input :self_governance, as: :select, label: "Select your governance approach", collection: Preference.self_governances.map { |key, val| [key.humanize, key] } } %>
If we look at just the output of:
Preference.self_governances.map { |key, val| [key.humanize, key] }
We get the following:
[
["Tier 1", "tier_1"],
["Tier 2", "tier_2"]
...
]
Note that the first value is what gets shown as the select label and the second value is what gets sent to your controller within the param.
EDIT:
When using an enum, you can assign either the key or value of the enum to the field.
preference.self_governance = 1 # Works
preference.self_governance = :tier_1 # Works
preference.self_governance = "tier_1" # Works
But you can't assign the value as a string:
preference.self_governance = '1'
=> "ArgumentError: '1' is not a valid self_governance" # Doesn't work, tries to look for key '1' in enum, but doesn't exist.
So make sure you pass the key of the selected enum (i.e. "tier_1") aback to your form or else you ma

simple_form_for custom label_for

I've a select that has to provide some decimal values (percentages) for an accounting application.
The values are like 10, 20, 22.5.
The problem is that want to display the first two values with no decimal part, the third with it.
In the simple_form_for documentation, in the Collections section, it talks about a label_method, and i guess it would solve the problem.
Can you provide an example of usage of such a method?
It would be enough, but for clarity my simple_form_for is something like this :
<%= simple_form_for(#company) do |f| %>
...
<%= f.input :percentage, :collection=>[BigDecimal(10),BigDecimal(20),BigDecimal(22.5,3)]%><br />
...
And here is what i get:
Summarized, label_method allows you to define a method, which is called for each of the items in the given collection, and of which the result is used to display the item
Either you give a symbol as label method, like
label_method: :method_to_be_called
or you define the method in place, like
label_method: lambda { |value| calculated_label }
In your case, I would define the collection as
[ [BigDecimal(10), 0], [BigDecimal(20), 0], [BigDecimal(22.5, 3), 2] ]
and the label_method as
label_method: lambda { |value| number_with_precision(value[0], precision: value[1]) }
You probably also need a value_method, returning the right result to your server, something like
value_method: lambda { |value| value[0] }

Rails form_for selectbox with defined values view value from db

I have such form partial for CRUD actions:
= form_for #vehicle do |f|
= f.select :fuel_type, [["Выбрать", "None"], "Бензин", "Дизель", "Газ", "Гибрид", "Электричество", "Другое"], {:required => true, :selected => #vehicle.fuel_type}
So as you can see in db i store hole word, as for example Бензин or Дизель etc...
But when i try to edit using my form partial some data, i see that as default selected value of my selectbox is None (displayed as Выбрать). But when in db that field is Бензин why it is set to None in form view? How to see Бензин?
Also note that for new action #vehicle.fuel_type will be empty
I would pair the labels with a value, and store the value in the database as an integer:
[['Бензин', 1], ['Дизель', 2], ['Газ', 3]]
You can include blank on your collection to select none:
f.select :fuel_type, [['Бензин', 1], ['Дизель', 2], ['Газ', 3], ...], { :include_blank => "Выбрать" }
In this way you won't need :selected => #vehicle.fuel_type on your edit form as the value will be populated from the model if selected.
There is probably a better way to handle it and this functionality could also be done in the model, but to display the saved value you could create a helper method:
def map_fuel_type(value)
case value
when 1
'Бензин'
when 2
'Дизель'
when 3
'Газ'
when 4
'Гибрид'
when 5
'Электричество'
when 6
'Другое'
end
end
And then to show the value on your index or show page:
<%= map_fuel_type #vehicle.fuel_type %>
To store as string:
f.select :fuel_type, ["Бензин", "Дизель", "Газ", "Гибрид", "Электричество", "Другое"], { :include_blank => "Выбрать" }

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

Resources