Rails/Ruby - Confused by this enum-related error - ruby-on-rails

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>

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

Get association for the nested model in simple_form

I don't know have to get select box for the association and value from the other model.
For example i have a table course_names to record course name.
I have other table which has other table course which has more columns with course_name_id
class Course < ActiveRecord::Base
belongs_to :course_name
end
Now, i have to create student_profile and have association for courses table like this.
class Student < ActiveRecord::Base
belongs_to :course
...
end
And in the view with the simple form i have to create a select box for to select course.
<%= simple_form_for(#student_profile) do |f|%>
<% f.error_notification %>
<div class="form-inputs">
<%= f.association :course %>
But this association don't show course name.
<select class="select optional form-control" id="student_course_id" name="student[course_id]"><option value=""></option>
<option value="3">#<Course:0x00000105c23818></option></select>
But i want the output as (course-name for the option from course_names)
<select class="select optional form-control" id="student_course_id" name="student[course_id]">
<option value=""></option>
<option value="3">course-name</option>
</select>
How, do i achieve this.
one, solution is
<%= f.association :course,collection:Course.all.includes(:course_name).map{|a|[a.course_name.name,a.id]} %>
but this doesn't seem right? How do i tell association go get course name from the association.
have you tried to use this : label_method like that :
<%= f.association :course, label_method: :course_name %>
Or as course belongs to course_name, maybe you want this:
<%= f.association :course, label_method: lambda { |course| "#{course.course_name.name}"} %>
Edit To avoid all the queries, you should preload the data
courses = Course.includes(:course_name)
And then use it as follow
<% f.association :course, collection: courses, label_method: lambda { |course| "#{course.course_name.name}" } %>

Saving enum from select in Rails 4.1

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

Rails HABTM order with select boxes

I have two models (code redacted for clarity):
class App < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :apps
end
I want to be able to add multiple products to each app. Also, the order of the products in the app is important, and where I'm having trouble.
I'm using simple_form and I've tried using the association method:
<%= simple_form_for(#app) do |form| %>
<%= form.input :name %>
<%= form.association :products %>
<%= form.button :submit %>
<% end %>
It works wonderfully, but the select box it creates is not orderable. My attempt at a solution was to create multiple <select> boxes and order them somehow. This so far isn't working. As a test I tried this:
<%= form.collection_select('product_ids', Product.all, :id, :name, {}, {name: 'app[product_ids][]'}) %>
<%= form.collection_select('product_ids', Product.all, :id, :name, {}, {name: 'app[product_ids][]'}) %>
<%= form.collection_select('product_ids', Product.all, :id, :name, {}, {name: 'app[product_ids][]'}) %>
<%= form.collection_select('product_ids', Product.all, :id, :name, {}, {name: 'app[product_ids][]'}) %>
It actually does make the association in the database, but it 'selects' all the products for each select tag which ends up just selecting the last product on all of them:
<select id="app_product_ids" name="app[product_ids][]">
<option selected="selected" value="1">Golf Club</option>
<option value="20">Music Disc</option>
<option selected="selected" value="17">Magic Wand</option>
<option value="15">Nike Running Shoes</option>
<option selected="selected" value="19">Watch</option>
<option selected="selected" value="16">Wooden Spoon</option> // this is what is selected in the browser
</select>
Eventually, I'll make the adding and removal of these dynamic.
So my question is, how can I make this work? One way would be to have each <select> associate to the particular product instead of all the products. Maybe there's a better way all together.

form multiple select

I am learning rails and have been struggling with this for over a day now and can not figure out how to get this to work. I want a select box in my form that can select multiple elements. I have this working with this code:
<div class="field">
<%= f.label :products %><br />
<%= f.select :products, {"A"=>1, "B"=>2, "C"=>3, "D"=>4},{},:size=>5,:multiple=>true %>
</div>
This works fine and produces this HTML:
<div class="field">
<label for="script_products">Products</label><br />
<select id="script_products" multiple="multiple" name="script[products][]" size="5">
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
<option value="4">D</option></select>
</div>
What I can figure out is how the results get sent/stored. In my view for the "show" action, if I simply print out :products I get this:
Products: --- - '1' - '2' - '3'
If I print out :products.inspect I get this:
Products: "---\n- '1'\n- '2'\n- '3'\n"
and the class is a string. I would think it would be stored as an array, but I can not get it to work. I don't know where the dashes or the newlines come from.
I would consider adding the options as a has-many relationship on the model. Thus you can iterate through them, attach them and involve them in a multiple select in a (IMO) better way.
I would do something like in my model:
has_many :special_options
In my view:
f.select :special_options, :multiple => true
This would avoid the problem of having to serialize and deserialize the objects before storing them.

Resources