Storing array data in jsonb field with Rails and Postgresql - ruby-on-rails

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

Related

Rails Form Not Updating the Attributes

Is this the correct way of defining form, let me know if I need to provide any more details.
This is the UserPreference forms in new.html.erb
<%= form_for :user_preference, url: user_preferences_path do |u|%>
<p>
<%= u.label :title %><br>
<%= u.text_field :title %>
</p>
<p>
<%= u.label :description %><br>
<%= u.text_field :description %>
</p>
<p> <%= u.label :back_ground_color %><br>
<select name="bgcolor" id="bgcolor">
<option value="#FF3300">Orange</option>
<option value="#00FF00">Green</option>
<option value="#0000FF">Blue</option>
<option value="#FF0066">Pink</option>
<option value="#FFFF00">Yellow</option>
<option value="#FFFFFF">White</option>
</select>
</p>
<p>
<%= u.label :font %><br>
<select name="font" id="font">
<option value="Times New Roman">Times new Roman</option>
<option value="Verdana">Verdana</option>
<option value="Arial">Arial</option>
<option value="sans-serif">serif</option>
</select>
</p>
<br >
<p>
<%= u.submit %>
</p>
I am getting title and description when I am trying to render in html,
the attribute is not getting updated in database.
UserPreference.controller.rb
class UserPreferencesController < ApplicationController
def new
#user_preference = UserPreference.new
end
def create
#user_preference = UserPreference.new(userp_params)
#user_preference.save unless user_signed_in?
render plain: params[:user_preference].inspect
end
def edit
end
def update
end
private
def userp_params
params.require(:user_preference).permit(:title, :bgcolor, :font, :description)
end
end
When you say this
<%= u.text_field :description %>
because you are working with :user_preference, it will make an input like
<input type="text" name="user_preference[description]" value="foo">
(where 'foo' is the current value, or maybe it's blank)
Note the "name" attribute: this will go into params like
params = {:user_preference => {:description => "foo"}}
If you are going to hand-code the select then you need to make sure the name attribute has this structure too, eg
<select name="user_preference[bgcolor]" id="bgcolor">
So you will get params like
params = {:user_preference => {:description => "foo", :bgcolor => "#FFFFFF"}}
Which then allows you to say
#user_preference.attributes = params[:user_preference]
which is the standard way to deal with this in the controller.
However, rather than writing out all the html for the select, it's much nicer to use the rails form helpers (select in this case) like you do with the text fields. You can also define the options for the select with the options_for_select helper, which saves a lot of typing too.
<%= u.select :bgcolor, options_for_select([["Orange", "#FF3300"], ["Green", "#00FF00"], ["Blue", "#0000FF"], ["Pink", "#FF0066"], ["Yellow", "#FFFF00"], ["White", "#FFFFFF"]]) %>
It's also cleaner to define this variable of options in your code somewhere, eg in a UserPreference class method:
class UserPreference < ActiveRecord::Base
#class methods section
class << self
def bgcolor_options
[["Orange", "#FF3300"], ["Green", "#00FF00"], ["Blue", "#0000FF"], ["Pink", "#FF0066"], ["Yellow", "#FFFF00"], ["White", "#FFFFFF"]]
end
end
end
Now you can use the select like so:
<%= u.select :bgcolor, options_for_select(UserPreference.bgcolor_options) %>
http://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags

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>

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.

Rails can't save value from radio buttons

I have this line in a form made with Simple Form:
<fieldset>
<legend>Any acessory?</legend>
<li><%= f.input :has_acessory, label: false, collection: ["0","1"], as: :radio_buttons, input_html:{ name: 'has_acessory'} %></li>
</fieldset>
and Simple Form generates this code:
<fieldset>
<legend>Any Acessory?</legend>
<li><div class="input radio_buttons optional lending_has_acessory"><label class="radio"><input checked="checked" class="radio_buttons optional" id="lending_has_acessory_0" name="has_acessory" type="radio" value="0" />0</label><label class="radio"><input class="radio_buttons optional" id="lending_has_acessory_1" name="has_acessory" type="radio" value="1" />1</label></div></li>
</fieldset>
I'm using SQLite3, Where there's a table called lendings with an Integer column called has_acessory and it's default value is: 0
In Lending model:
class Lending < ActiveRecord::Base
attr_accessible :devolution_date, :lending_date, :situation, :description,:has_acessory, :person_id, :equipment_id
#Associations
belongs_to :equipment
belongs_to :person
end
But it doesn't matter which value that I choose in the radio buttons,I always get "0" (the default value)in the has_acessory column.I already checked the params, and I can find "has_acessory" =>"0" or "1".
For me this part is working pretty well, but why can't it be saved in the has_acessory column?
I would try to let rails do more of the work for you.
Please try just <%= f.input :has_acessory %> and see what that gives you.
Having all this <%= f.input :has_acessory, label: false, collection: ["0","1"], as: :radio_buttons, input_html:{ name: 'has_acessory'} %> makes it hard to debug.
Please look at the migration/database and make sure that the field is boolean. If it is, rails will figure out all the right options for you.
Also - you say that the field has a default - but I don't see that in the model. Did you just omit that line in the model? Is it in both the rails model and the database?
Try this
Add this line into the controller method
params[:has_acessory] = params[:has_acessory].to_i

Resources