How to pass value of multiple checkbox to a model? - ruby-on-rails

i've in a rails form a multiple checkbox values.
I need to pass this values (if checked) to a model that create the categories.
I've this in form.html_erb
<%= form.check_box :categories, {multiple: true}, "U6", nil %>
<%= form.check_box :categories, {multiple: true}, "U8", nil %>
<%= form.check_box :categories, {multiple: true}, "U10", nil %>
I want to create TeamCategory with the values checked.
Something like this?
def create_tournament_team_categories
VALUE_CHECKED.each do |name|
team_category = TeamCategory.where(name: VALUE_CHECKED).first_or_create
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
Now the TeamCategory are created automatically with this:
def create_tournament_team_categories TeamCategory::NAMES.each do |name|
team_category = TeamCategory.where(name: name).first_or_create
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
And in TeamCategory model i've:
NAMES = %w[U6 U8 U10 U12 U14 U16].freeze

Use the collection_check_boxes helper:
form.collection_check_boxes(:category_ids, TeamCategory.all, :id, :name)
_ids= is a special setter created by the has_many and HABTM macros. You just pass an array of ids and rails will handle creating the join records for you.
Just make sure you whitelist the array:
params.require(:tournament).permit(:foo, :bar, category_ids: [])

Related

Using model constants as groups in Rails grouped_collection_select

I'm building a form where users create a financial transaction. One field is a drop down list of bank accounts.
I would like this dropdown list to group all listed Bank Accounts by each account's account type (BankAccount::ACCOUNT_TYPE - an attribute in each BankAccount record).
If I manually code everything right now, the code would look like this:
<%= f.select :bank_account_id,
{
'On-Budget' => ['Cash',
'Credit Card 1',
'Credit Card 2',
'Venmo'],
'Off-Budget' => ['Investment Bank 1',
'Investment Bank 1'],
'Closed' => ['Old Bank 1',
'Old Bank 2'],
} %>
app/models/bank_account.rb - where I define ACCOUNT_TYPES
class BankAccount < ApplicationRecord
ACCOUNT_TYPES = %w(On-Budget Off-Budget Closed).freeze
...
end
Here is my working collection.select, without grouping
<%= f.collection_select :bank_account_id,
BankAccount.all,
:id,
:name,
{prompt: 'Select an account'} %>
From the rails API, I think grouped_collection_select is what I need
(https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-grouped_collection_select)
grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
Using BankAccount::ACCOUNT_TYPES as my group_method, and group_label_method doesn't work.
<%= f.grouped_collection_select(:bank_account_id,
BankAccount.all,
BankAccount::ACCOUNT_TYPES, (group_method?)
BankAccount::ACCOUNT_TYPEs, (group_label_method?)
:id,
:name,
{prompt: 'Select an account' } %>
I found the answer from this SO question: https://stackoverflow.com/questions/36854817/use-grouped-collection-select-to-group-a-model-by-enum
I created a helper method
def grouped_bank_account_options_for_select(selected_bank_account_id = nil)
options = {}
BankAccount::ACCOUNT_TYPES.each do |t|
unless t == "Closed"
options[t] = BankAccount.where(account_type: t)
.pluck(:name, :id)
end
end
grouped_options_for_select(options, selected_bank_account_id)
end
and then called it in the view's form helper
<%= select_tag :bank_account_id, grouped_bank_account_options_for_select(params[:bank_account_id]), {prompt: 'Select an account'} %>

ruby on rails: include blank if more than one

In a form, I want to include blank only in case of Client.count>1. Is there a clean way of doing this?
Now I'm using this select:
= f.select :client_id, Client.all.map{|c| [c.full_name, c.id]}, {include_blank: true}
You can use a tiny decorator:
class ClientDecorator
def self.form_select_choices
Client.pluck(:full_name, :id)
end
def self.form_select_include_blank?
{ include_blank: Client.count.positive? }
end
end
So, in your view you call those class methods:
<%= form.select :client_id, ClientDecorator.form_select_choices, ClientDecorator.form_select_include_blank? %>
Now you can test that and leave the database interaction far from the views.

Rails4: collection_check_boxes from array

There is some way to serialize a collection_check_boxes from one constant?
Something like this:
# model
class tutorial < ActiveRecord::Base
serialize :option
TYPES = ["Option 1", "Option 2", "Option 3"]
end
# view
<%= form_for(#tutorial) do |b| %>
<%= f.collection_check_boxes(:option, Tutorial::TYPES, :id, :name) do |b| %>
<%= b.label class:"label-checkbox" do%>
<%=b.check_box + b.text%>
<%end%>
<% end %>
<% end %>
Or just:
<%= f.collection_check_boxes :option, Tutorial::TYPES, :id, :name %>
When I try both it I get the error:
undefined method `id' for "Option\t1":String
My permit parameters are already set with option: []
Someone did something like that before?
Thanks!
The definition is:
collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)`
The first one is a method to send, the second is a collection, the third is a method which is called to set an option value property, and the fourth is a method that is called to get a text and place it as a label for an option.
<%= f.collection_check_boxes :option, Tutorial::TYPES, :id, :name %>
There you are using Tutorial::TYPES (which is an array if strings) as a collection, and call id and name methods on each string.
Your collection should be Tutorial.all, and to get a label, you should implement a method on a Tutorial object for that, for example:
enum type: [
:type1,
:type2,
:type3,
]
And use it like this:
<%= f.collection_check_boxes :option, Tutorial.all, :id, :type %>

Editing a single attribute with more than one form field

Background: Users and communities share a
has_many :through
relationship. Each community has a "community_type" string that identifies it (ie "Gender", "City", etc.).
Objective: In my edit form, I'd like to allow the user to edit his :community_ids based on community type. Something like:
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_ids, Community.filtered_by("Gender"), :id, :name) %>
<%= f.collection_select(:community_ids, Community.filtered_by("City"), :id, :name) %>
<% end %>
The issue is that the form only accepts the last form field value for :community_ids - in this case being the "City" - rather than merging all of them as one big :community_ids array.
Solution:
For those interested, I ended up refactoring my model code from the answers below to this:
%W[ community1 community2 community3 community4 ].each do |name|
define_method "#{name}" do
self.communities.filtered_by("#{name}").map(&:name)
end
define_method "#{name}_ids" do
self.communities.filtered_by("#{name}").map(&:id)
end
define_method "#{name}_ids=" do |val|
self.community_ids += val
end
end
Is there a reason you're using select boxes for a has_many relationship? It seems checkboxes would be more appropriate. If you want to go with select boxes, I don't think you can use FormHelper#select, because as far as I know, it's expecting a single value, and your community_ids is an array. This is why it's only picking one of the values you give it.
For a select box (or any field), you can combine the values across fields by adding [] to the parameter name which tells Rails that the parameter is an array of values. You can do this by using regular select_tag to create the fields, and setting the parameter name as follows:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
You could also go with Ryan's approach of sending separate parameters, though one downside is your User model will have to be very aware of the types of communities that exist, and you'll have to write separate logic in the User model for each type of community. This will make your resources less modular. But if you do go that way, I'd suggest using pseudo-attributes instead of a before_save so that your community_ids get updated automatically from the params:
class User < ActiveRecord::Base
...
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
...
end
And then your select_tag calls would look something like this:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_gender_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_city_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
Updated to complete tsherif's (better than my original) answer.
view.rb
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_gender_ids, Community.filtered_by("Gender"), :id, :name, {}, id: 'community-gender-options') %>
<%= f.collection_select(:community_city_ids, Community.filtered_by("City"), :id, :name, {}, id: 'community-city-options') %>
<% end %>
model.rb
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
def community_gender_ids
self.communities.select(:id).where(:community_type => 'gender').map(&:id)
end
def community_city_ids
self.communities.select(:id).where(:community_type => 'city').map(&:id)
end
Alternatively, you could write some CoffeeScript/Javascript to bind to the select tags and add the IDs to a hidden value which is then submitted to the server with the form.

How to edit a Rails serialized field in a form?

I have a data model in my Rails project that has a serialized field:
class Widget < ActiveRecord::Base
serialize :options
end
The options field can have variable data info. For example, here is the options field for one record from the fixtures file:
options:
query_id: 2
axis_y: 'percent'
axis_x: 'text'
units: '%'
css_class: 'occupancy'
dom_hook: '#average-occupancy-by-day'
table_scale: 1
My question is what is the proper way to let a user edit this info in a standard form view?
If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.
What is the best/proper way to edit a serialized hash field like this in Rails?
If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:
class Widget < ActiveRecord::Base
serialize :options
def self.serialized_attr_accessor(*args)
args.each do |method_name|
eval "
def #{method_name}
(self.options || {})[:#{method_name}]
end
def #{method_name}=(value)
self.options ||= {}
self.options[:#{method_name}] = value
end
attr_accessible :#{method_name}
"
end
end
serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end
The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:
#haml
- form_for #widget do |f|
= f.text_field :axis_y
= f.text_field :axis_x
= f.text_field :unit
Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.
However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).
You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.
Since I included " || {}", it will work with new/create forms, too.
= form_for #widget do |f|
= f.fields_for :options, OpenStruct.new(f.object.options || {}) do |f2|
= f2.text_field :axis_y
= f2.text_field :axis_x
= f2.text_field :unit
Have a great day
emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.
Here it is from top to bottom:
class Widget < ActiveRecord::Base
serialize :options, Hash
end
<%= form_for :widget, #widget, :url => {:action => "update"}, :html => {:method => :put} do |f| %>
<%= f.error_messages %>
<%= f.fields_for :options do |o| %>
<%= o.text_field :axis_x, :size => 10, :value => #widget.options["axis_x"] %>
<%= o.text_field :axis_y, :size => 10, :value => #widget.options["axis_y"] %>
<% end %>
<% end %>
Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.
I've been struggling with a very similar problem. The solutions I found here were very helpful to me. Thank you #austinfromboston, #Christian-Butske, #sbzoom, and everyone else. However, I think these answers might be slightly out-of-date. Here's what worked for me with Rails 5 and ruby 2.3:
In the form:
<%= f.label :options %>
<%= f.fields_for :options do |o| %>
<%= o.label :axis_y %>
<%= o.text_field :axis_y %>
<%= o.label :axis_x %>
<%= o.text_field :axis_x %>
...
<% end %>
and then in the controller I had to update the strong parameters like so:
def widget_params
params.require(:widget).permit(:any, :regular, :parameters, :options => [:axis_y, :axis_x, ...])
end
It seems to be important that the serialized hash parameter comes at the end of the list of parameters. Otherwise, Rails will expect the next parameter to also be a serialized hash.
In the view I used some simple if/then logic to only display the hash if it is not empty and then to only display key/value pairs where the value was not nil.
I was facing the same issue, after some research i found a solution using Rails' store_accessor to make keys of a serialized column accessible as attributes.
With this we can access "nested" attributes of a serialized column …
# post.rb
class Post < ApplicationRecord
serialize :options
store_accessor :options, :value1, :value2, :value3
end
# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"
# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)
# form.html.erb
<%= form_with model: #post, local: true do |f| %>
<%= f.label :value1 %>
<%= f.text_field :value1 %>
# …
<% end %>
No need setter/getters, I just defined in the model:
serialize :content_hash, Hash
Then in the view, I do (with simple_form, but similar with vanilla Rails):
= f.simple_fields_for :content_hash do |chf|
- #model_instance.content_hash.each_pair do |k,v|
=chf.input k.to_sym, :as => :string, :input_html => {:value => v}
My last issue is how to let the user add a new key/value pair.
I will suggest something simple, because all the time, when user will save form You will get string. So You can use for example before filter and parse those data like that:
before_save do
widget.options = YAML.parse(widget.options).to_ruby
end
of course You should add validation if this is correct YAML.
But it should works.
I'm trying to do something similar and I found this sort of works:
<%= form_for #search do |f| %>
<%= f.fields_for :params, #search.params do |p| %>
<%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect { |pt| [ pt.value, pt.id ] } %>
<%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
<%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
<% end %>
<% end %>
except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:
#search = Search.new(params[:search])
so its "half" working...

Resources