Saving enum from select in Rails 4.1 - ruby-on-rails

I am using the enums in Rails 4.1 to keep track of colors of wine.
Wine.rb
class Wine < ActiveRecord::Base
enum color: [:red, :white, :sparkling]
end
In my view, I generate a select so the user can select a wine with a certain color
f.input :color, :as => :select, :collection => Wine.colors
This generates the following HTML:
<select id="wine_color" name="wine[color]">
<option value=""></option>
<option value="0">red</option>
<option value="1">white</option>
<option value="2">sparkling</option>
</select>
However, upon submitting the form, I receive an argument error stating '1' is not a valid color. I realize this is because color must equal 1 and not "1".
Is there a way to force Rails to interpret the color as an integer rather than a string?

Alright, so apparently, you shouldn't send the integer value of the enum to be saved. You should send the text value of the enum.
I changed the input to be the following:
f.input :color, :as => :select, :collection => Wine.colors.keys.to_a
Which generated the following HTML:
<select id="wine_color" name="wine[color]">
<option value=""></option>
<option value="red">red</option>
<option value="white">white</option>
<option value="sparkling">sparkling</option>
</select>
Values went from "0" to "red" and now we're all set.
If you're using a regular ol' rails text_field it's:
f.select :color, Wine.colors.keys.to_a
If you want to have clean human-readable attributes you can also do:
f.select :color, Wine.colors.keys.map { |w| [w.humanize, w] }

No need converting the enum hash to array with to_a. This suffice:
f.select :color, Wine.colors.map { |key, value| [key.humanize, key] }

The accepted solution didn't work for me for the human readable, but I was able to get it to work like this:
<%= f.select(:color, Wine.colors.keys.map {|key| [key.humanize, key]}) %>
This was the cleanest, but I really needed to humanize my keys:
<%= f.select(:color, Wine.colors.keys) %>

I just put together an EnumHelper that I thought I'd share to help people who need more customised enum labels and locales for your enum selects.
module EnumHelper
def options_for_enum(object, enum)
options = enums_to_translated_options_array(object.class.name, enum.to_s)
options_for_select(options, object.send(enum))
end
def enums_to_translated_options_array(klass, enum)
klass.classify.safe_constantize.send(enum.pluralize).map {
|key, value| [I18n.t("activerecord.enums.#{klass.underscore}.#{enum}.#{key}"), key]
}
end
end
In your locale:
en:
activerecord:
enums:
wine:
color:
red: "Red Wine"
white: "White Wine"
In your views:
<%= f.select(:color, options_for_enum(#wine, :color)) %>

If you use enum in Rails 4 then just call Model.enums:
f.select :color, Wine.colors.keys
To create HTML:
<select name="f[color]" id="f_color">
<option value="red">red</option>
<option value="white">white</option>
<option value="sparkling"> sparkling </option>
</select>
Or add method in controller:
def update_or_create
change_enum_to_i
....
end
def change_enum_to_i
params[:f]["color"] = params[:f]["color"].to_i
end

If you need to handle the i18n based on the enum keys you can use:
<%= f.select :color, Wine.colors.keys.map {|key| [t("wine.#{key}"), key]} %>
and in the tranlations you can set the colors:
wine:
red: Red
white: White

Here is what worked for me, Rails 4+:
class Contract < ApplicationRecord
enum status: { active: "active",
ended: "active",
on_hold: "on_hold",
terminated: "terminated",
under_review: "under_review" ,
unknown: "unknown"
}
end
in my _form.html.erb , I have this:
<div class="field">
<%= form.select :status, Contract.statuses.keys, {}%>
</div>
test from Console after adding a record:
2.3.0 :001 > Contract.last.status
Contract Load (0.2ms) SELECT "contracts".* FROM "contracts" ORDER BY "contracts"."id" DESC LIMIT ? [["LIMIT", 1]]
=> "active"

Here's my solution (my roles have underscores in them like "sales_rep"), and for some reason this was how I needed to get a blank option to work (with simpleform?):
In ApplicationHelper:
def enum_collection_for_select(attribute, include_blank = true)
x = attribute.map { |r| [r[0].titleize, r[0]] }
x.insert(0,['', '']) if include_blank == true
x
end
Then in my form:
<%= f.input :role, collection: enum_collection_for_select(User.roles), selected: #user.role %>

for me the following worked as well:
= f.input :color, collection: Wine.colors.keys.map{ |key| [key.humanize, key] }, include_blank: false

Related

Storing array data in jsonb field with Rails and Postgresql

Say I have a Car model in which I want to display different kinds of data. I add a data column to the table:
class AddDataToCars < ActiveRecord::Migration[5.0]
def change
add_column :cars, :data, :jsonb, null: false, default: '{}'
add_index :cars, :data, using: :gin
end
end
Then I add an accessor for a field I want to be present (extra_options):
class Car < ApplicationRecord
store_accessor :data, :wheel_count, :extra_options
validates :extra_options, presence: true
end
I make sure it's a permitted parameter in cars_controller.rb:
def car_params
params.require(:car).permit(:make, :model, :data, :wheel_count, :extra_options)
end
Now I can create a text input in my _form.html.erb partial that puts data in wheel_count like this:
<div class="field">
<%= f.label :wheel_count %>
<%= f.text_field :wheel_count %>
</div>
And it works as expected. What I then would like is a select list (multiple select) or a set of checkboxes where the selected options are stored under extra_options.
I've tried with:
<%= f.select :extra_options, options_for_select(["1", "2", "3", "4", "5"], car.extra_options), { include_blank: true }, { multiple: true } %>
which produced the following markup:
<input name="car[extra_options][]" type="hidden" value="" />
<select multiple="multiple" name="car[extra_options][]" id="car_extra_options">
<option value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
Obviously the option labels should make more sense than just 1, 2, 3, 4 and 5, but what's worse is that nothing gets stored when I submit the form.
On submission if I select 2 and 3 the parameters look like:
"extra_options"=>["", "2", "3"]
which results in the message Unpermitted parameter: extra_options so it seems like I am not allowed to put arrays in this field.
It may seem stupid to create a jsonb field just for some extra options but it would naturally also hold all sorts of other data.
So how can I create a multiple select list or preferably a set of checkboxes that saves the data correctly in the jsonb field (and of course when editing a submission shows the already selected/checked items)?
Try this, in car_params
params.require(:car).permit(:make, :model, :data, :wheel_count, extra_options: [])
For checkboxes, try this
<%= hidden_field_tag "car[extra_options][]", [] %>
<% ["1", "2", "3", "4", "5"].each do |o| %>
<% default_checked = car.extra_options.include?(o.to_s) rescue false %>
<label class="rightpad">
<%= check_box_tag "car[extra_options][]", o, default_checked %>
</label>
<span>
<%= o %>
</span>
<% end %>

Rails/Ruby - Confused by this enum-related error

I've created a concern that contains an enum listing possible gender values for use in forms and such:
genderable.rb
module Genderable
extend ActiveSupport::Concern
included do
enum gender: [:"Not Known", :"Male", :"Female", :"Not Applicable"]
end
end
Here's where I include it in my model:
user.rb
class User < ActiveRecord::Base
include Genderable
has_one :user_gender
has_one :gender, :through => :user_gender
accepts_nested_attributes_for :user_gender
end
Here's where I use it within my view:
edit.html.erb
<div class="form-group">
<%= f.select :gender, User.genders, :class => "form-control" %>
<%= showErrorMessages("Gender", :gender) %>
</div>
Here's the markup that is generated for my view:
<select id="user_gender" name="user[gender]">
<option value="0">Not Known</option>
<option value="1">Male</option>
<option value="2">Female</option>
<option value="3">Not Applicable</option>
</select>
Here's the error message I get when attempting to submit the form relying upon all of the above:
It seems that ActiveRecord dislikes the fact that the value for gender is a string and not an integer (I'm guessing!). Or maybe it dislikes that the value doesn't match one of the symbol values of the enum I created. I'm really not sure. Does anyone know what the problem is? Basically, I just want to update the user_genders table with the user's ID and the selected value.
You can't assign the integer values to an enum. You need to assign the actual symbol.
For your view, you should use:
<%= f.select :gender, User.genders.keys, :class => "form-control" %>
In order to generate a select that looks like this:
<option value="Not Known">Not Known</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
<option value="Not Applicable">Not Applicable</option>

Add value to select in rails

i have a select that take values from database.
<%= select_tag :location, options_from_collection_for_select(Country.all, :id, :country_name), { :class => 'selectpicker' } %>
So i get all countries from database.
How i can do for add a custom value(for example Any, with value 0), to this select list taken from database ?
For example now i have:
<select>
<option value="UK">UK</option>
</select>
and i want get this:
<select>
<option value="0">Any</option>
<option value="UK">UK</option>
</select>
Thanks.
You can use the either prompt or include_blank options (FormOptionsHelper) as follows:
<%= select_tag :location, options_from_collection_for_select(Country.all, :id, :country_name), :prompt => 'Any', :class => 'selectpicker' %>

How do I add HTML attributes to select options with Simple Form Rails?

I need to add a custom HTML attribute to each option for a select control. I'm using simple_form in Rails. Does anyone know how to do this? The attribute will be consumed by client-side JS.
For instance, I want to do something like this:
<%= f.input :group, collection: #groups, option_html: { data-type: lambda { |g| g[2] } } %>
Which would produce (simplified):
<select>
<option value="1" data-type="primary">First Group</option>
<option value="2" data-type="secondary">Second Group</option>
<option value="3" data-type="secondary">Third Group</option>
</select>
Where #groups might look like this:
[
['First Group', 1, 'primary'],
['Second Group', 2, 'secondary'],
['Third Group', 3, 'secondary']
]
Hoping to avoid having to make a custom control/wrapper. Thanks!
You're close! easiest way is actually not using simple_form here. here's the simple_form documentation
<% options = #group.map { |g| [g.name, g.id, {'data-type' => g.group_type}] } %>
<%= f.input :group, label: 'Group' do %>
<%= f.select :group, options, include_blank: 'Select a Group', class: 'form-control' %>
<% end %>
For your exact code it would be:
<% options = #group.map { |g| [g[0], g[1], {'data-type' => g[2]}] } %>
<%= f.input :group, label: 'Group' do %>
<%= f.select :group, options, include_blank: 'Select a Group', class: 'form-control' %>
<% end %>
simple-form only:
= f.input :group, #groups.map{|l| [l[0], l[1], {data: {type: l[2]}}]}
A (small) drawback using f.input do end method is that any default input html options (like simple form's required or optional classes or the required attribute) and any default options (like b.use :input, class: 'input-element') are missing when simply passing a block to f.input, tldr: the input does not get decorated.
If you rely on these extra classes and attributes, you'd have to manually pass them in (not dry).
To overcome this I've created a custom input for my special selects, so I can define the body of my select like I want (the <option> tags) but the select gets decorated as usual:
# app/inputs/select_container_input.rb
class SelectContainerInput < SimpleForm::Inputs::Base
def input(wrapper_options)
options_html = input_options.delete(:options_html)
# since we pass our options by our self (and have to select the correct
# option), set `selected` to `''` to prevent rails calling
# `object.send(attribute_name)` only to set `selected` which is not used.
input_options[:selected] = ''
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
#builder.select attribute_name, nil, input_options, merged_input_options do
options_html
end
end
end
Simply call it like this:
<% options_html = capture do %>
<option>bla</option>
<% end %>
<%= f.input :attribute, as: :select_container, options_html: options_html %>
The options_html is a workaround, because actually it would be easier to pass in a block to our custom input:
<%= f.input :attribute, as: :select_container do %>
<option>bla</option>
<% end %>
But due to the way SimpleForm::FormBuilder#def_input works, the block gets carried away before code even touches the inputs. So no way without refactoring simple_form.
All in all this solves the problem with a little bit extra noisy code in your views for your special selects.
For an association select:
f.association :product, collection: Product.all.map { |product| [product.name, product.id] }
This seems to be the correct way of doing this:
Rails Simple Form custom association select field

Rails select form helper

I am probably missing something really simple, but would appreciate a little help here
<%= select_tag('filters[order_sales_channel]',filter_params['order_sales_channel'],:multiple=>true,:class=>"advancedSearchFormSelectBox") %>
I then want to give the options for select
<option value="Web">Web</option>
<option value="Phone">Phone</option>
How can I achieve this? I tried this but it wasn't working
<%= select_tag('filters[order_sales_channel]',filter_params['order_sales_channel'],:multiple=>true,:class=>"advancedSearchFormSelectBox") %>
<%= options_for_select([["Web", "Web"], ["Phone", "Phone"]]) %>
Follow up question -: Since I am using the select option in multiple places where my option value is same as the printed string, is there a better way to go about this?
The correct way to do this is :
<%= select_tag 'filters[order_sales_channel]',
options_for_select(
[["Web", "Web"], ["Phone", "Phone"]],
selected: filter_params['order_sales_channel']
),
multiple: true,
class: "advancedSearchFormSelectBox" %>
if your options will always be the same, just hardcode them in the relevant class:
class Order < ActiveRecord::Base
SELECT_OPTIONS = %w[Web Phone].map( &:freeze ).freeze
def self.select_options
SELECT_OPTIONS.map{|o| [o,o] }
end
end
then you can use this in your options_for_select :
options_for_select Order.select_options
I like this solution because you can also use SELECT_OPTIONS in validations :
validates :some_attribute, inclusion: SELECT_OPTIONS

Resources