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.
Related
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: [])
I need to write a view that returns the model plus additional fields that is not part of the model.
In the controller I should access these like:
def create
#post = Post.new(params[:post])
#stuff = params[:stuff]
How can I write the view?
you can use fields_for helper
form_for #post do |f|
f.text_field :title
fields_for :stuff do |stuff_form|
stuff_form.text_field :name
end
end
remember, its simply fields_for not f.fields_for. if you use f.fields_for it will give the stuff_form fields as part of your post object params
Use form helpers, thus:
<%= form_for #post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body, size: "60x12" %>
<%= text_field_tag :stuff %>
<%= f.submit "Create" %>
<% end %>
For most types of input field, there's both the f.field_type and field_type_tag helpers. The former are for fields that are part of the model, whereas the latter are simply additional fields that will be present in the params. So the above would give you a params hash like this:
{:post => {:title => 'the title', :body => 'the contents of the post'}, :stuff => 'contents of stuff field'}
You can add field tags that are not part of your form which will add to the params hash.
in haml...
= text_field_tag :stuff, "what you want in stuff"
= hidden_field_tag :stuff, "or if you wanted a hidden field to show stuff"
will result in params hash...
stuff=>"what you want in stuff"
I have read a number of posts about this and I'm still confused. I'm relatively new to Rails. I'm on Rails 3.2.8 and Ruby 1.9.3.
I have a form where I want to create a record for 2 different tables. It's a many-to-many relationship (logbooks can have multiple aircraft and aircraft will appear in multiple logbooks).
Here's my code:
# ----- Models ----- #
class Logbook < ActiveRecord::Base
has_and_belongs_to_many :aircrafts
accepts_nested_attributes_for :aircrafts
end
class Aircraft < ActiveRecord::Base
belongs_to :logbook
end
# ----- Logbooks Controller ----- #
def my_method
#logbook = Logbook.new
#aircraft = Aircraft.new
end
# ----- View ----- #
<%= form_for #logbook, validate: true, remote: true do |f| %>
<%= f.label :flight_date, "Flight Date" %>
<%= f.text_field :flight_date %>
...
<%= f.fields_for :aircrafts, validate: true, remote: true do |a| %>
<%= a.label :aircraft_id, "Aircraft ID" %>
<%= a.text_field :aircraft_id %>
...
<% end %>
<% end %>
The logbook fields are rendering fine, but the aircraft fields don't render.
Any ideas?
Try changing the controller to:
def my_method
#logbook = Logbook.new
#aircraft = #logbook.aircrafts.build
end
Because #aircraft need belongs_to a Logbook, so the nested_form will know how to build the form.
Note: if you will not use the variable #aircraft you don't need to declare it, just use #logbook.aircrafts.build on controller
If i recall correctly, the fields_for will generate based off the number of objects in the has many that exist for the parent. Since your #logbook doesn't have any aircrafts, no fields appear. Try changing the action:
def my_method
#logbook = Logbook.new
#logbook.aircrafts << Aircraft.new
end
Now, if you need to make something in the UI to add more than one logbook, you need to create an add button. You can just increment the amount of aircraft records you build based off that:
<%= form_tag new_logbook_path, :method => :get do %>
<%= hidden_field_tag "amount", #amount %>
<%= submit_tag "Add" %>
<% end %>
Then the action would be:
def my_method
#amount = params[:amount] || 1
#logbook = Logbook.new
#amount.times { #logbook.aircrafts << Aircraft.new }
end
Sometimes we need form without model creation - for example search field or email, where should be send some instructions. What is the best way to create this forms? Can i create virtual model or something like this? I'd like to use formtastic, but not form_tag.
Firstly, Formtastic doesn't need a model in all cases, although it certainly works best and requires less code with a model.
Just like Rails' own built-in form_for, you can pass in a symbol instead of an object as the first argument, and Formtastic will build the form and post the params based on the symbol. Eg:
<% semantic_form_for(:session) do |f| %>
...
<% end %>
This will make the form values available to your controller as params[:session].
Secondly, a model doesn't mean an ActiveRecord model. What I mean is, Formtastic will work with any instance of a class that quacks like an ActiveRecord model.
A classic example of this that many people are using Authlogic for authentication with Formtastic. Part of Authlogic is the idea of a UserSession model, which works fine:
Controller:
def index
#user_session = UserSession.new
end
Form:
<% semantic_form_for(#user_session) do |f| %>
<%= f.input :login %>
<%= f.input :password %>
<% end %>
This will make your form data available in your controller as params[:user_session].
It's really not that hard to create a model instance to wrap up the concerns of your model. Just keep implementing the methods Formtastic is expecting until you get it working!
default_language.rb
class DefaultLanguage
attr_accessor :language_id
end
foo_controller.rb
def index
#default_language = params[:default_language] || Language.find_by_name("English")
end
index.erb
<% semantic_form_for #default_language do |form| %>
<% form.inputs :id => 'default_language' do %>
<%= form.input :id,
:as => :select,
:collection => #languages,
:required => false,
:label => "Primary Language:",
:include_blank => false %>
<% end %>
<% end %>
I used AJAX to post the form when the value changed.
Or you simply create a form with form_for and leave the model reference blank.
for example
<% form_for "", :url=>some_url do |f| %>
<%= f.text_field "some_attribute" %>
<%= submit_tag "submit" %>
You can fetch the values by simply saying params[:some_attribute] in your controller.
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...