Rails 4 : Checkbox array is not updating my attribute - ruby-on-rails

My starting place was this discussion: Syntax for form_for when building an array from checkboxes
I have a call to my model passing back an array of valid options. This array then makes a series of check_box_tag
<%= form_for #game, :url => wizard_path do |f| %>
<div>
<% #game.select_races.each do |a| %>
<%= f.label a %>
<%= check_box_tag 'game[races][]', a , true %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This successfully creates an array called 'races' containing the desired output. The problem is that it doesn't actually update the races attribute. So my races attribute is still nil.
I'm sure this is a painful Rails beginner question. Any help is appreciated.
UPDATE
My allowed params were:
def game_params
params.require(:game).permit(:shattered_empire, :shards_of_the_throne, :number_of_players, :rules, :strategy_cards, :players, :races)
end
Which needed to be updated to:
def game_params
params.require(:game).permit(:shattered_empire, :shards_of_the_throne, :number_of_players, :rules, :strategy_cards, :players, {:races => []})
end

Related

multiple records in one form - strong params issue

I want to create a several records with a single form, but I'm not sure how to make it work with strong params. I can make the strong params accept an array of hashes, but I'm not sure how to make the form post that, and I can make the form post a hash with integer keys, but I'm not sure how to make the strong params accept that.
I have a Question model and an Answer model. This is a questionnaire page, which shows all the Questions and lets the user create Answers to them.
<h1>Questionnaire</h1>
<%= form_with url: questionnaire_path, local: true do |form| %>
<% #questions.each_with_index do |question| %>
<%= fields_for "answers[]", question do |af| %>
<p><%= question.body %></p>
<% question.options&.each_with_index do |option, index| %>
<%= af.check_box :options, {multiple: true }, index, nil %>
<%= af.label :options, option %>
<%= af.hidden_field :question_id, value: question.id %>
<% end %>
<% end %>
<% end %>
<p><%= form.submit "Submit Answers" %></p>
<% end %>
I get back params that look like this:
"answers"=>{"0"=>{"options"=>["0"]}, "1"=>{"options"=>["0", "2"]}}
So I tried using this:
def answer_params
params.require(:answers).permit(:question_id, options: [])
end
But then I get an error:
Unpermitted parameters: :1, :2
I don't like this solution, but the workaround I've found so far is to change my fields_for to include a scope outside of answers, called questionnaire:
<%= fields_for "questionnaire[answers][]", question do |af| %>
Then change answer_params to this awkwardness:
def answer_params
params.require(:questionnaire).permit(answers: [:question_id, options: []])[:answers].values
end

Rails form with nested attributes. text_field not appearing

Issue: I have a nested fields_for text_field not appearing, I am not sure what I have been done wrong.
Goal: While creating a record, iterate through a model with preset variables, and save a file (testing with text_field) to a join table which saves both the preset variables and the forms record ID
Models:
class PrintLocation < ApplicationRecord
has_many :shop_products, through: :shop_product_print_files
has_many :shop_product_print_files
accepts_nested_attributes_for :shop_product_print_files
end
class ShopProductPrintFile < ApplicationRecord
belongs_to :shop_products
belongs_to :print_locations
end
class ShopProduct < ApplicationRecord
...
has_many :shop_product_print_files
has_many :print_locations, through: :shop_product_print_files
accepts_nested_attributes_for :print_locations
accepts_nested_attributes_for :shop_product_print_files
...
end
Form:
<%= form_for #shop_product do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<% PrintLocation.all.each do |print_location| %>
<%= print_location.title %>
<%= f.fields_for :shop_product_print_files do |a| %>
<%= a.text_field :print_file %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
With this, the text_field doesn't appear but the print_location.title's do appear. There are no errors with this.
While saving the #shop_product, I want to be able to iterate through the possible print_location variables, which are defined, and then for each possible print_location, to then be able to upload a file (text_field for testing), and then save that to the ShopProductPrintFile model which has shop_product_id and print_location_id and print_file attributes.
Is there something I am misunderstanding for how to use fields_for?
Shop Product Controller:
Create:
#shop_product = ShopProduct.new(shop_product_params)
shop = Shop.find(params["shop_product"]["shop_id"])
product = Product.find(params["shop_product"]["product_id"]) #shop_product.product_id = product.id
#shop_product.shop_id = shop.id
respond_to do |format|
if #shop_product.save!
...
Update:
#shop_product = ShopProduct.find_by(store_variant_id: params["shop_product"]["store_variant_id"])
#product = Product.find(params["shop_product"]["product_id"])
Strong Params:
def shop_product_params
params.require(:shop_product).permit(:product_id, :store_product_id, :shop_id, :store_variant_id, :sync, :shop_product_print_file_attributes[:id, :print_files, :print_location_ids => [], :shop_product_ids => []], {print_location_ids: []})
end
UPDATE 2:
Update and Create Method:
#shop_product.shop_product_print_files.build
form:
<% PrintLocation.all.each do |print_location| %>
<%= print_location.title %>
<%= f.fields_for :shop_product_print_files_attributes do |a| %>
<%= a.text_field :print_file %>
<%= a.hidden_field :print_location_id, value: print_location.id %>
<%= a.hidden_field :shop_product_id, value: shop_product.id %>
<% end %>
<% end %>
params:
def shop_product_params
params.require(:shop_product).permit(:shop_product_print_files_attributes => [:ids => [], :print_files => [], :print_location_ids => [], :shop_product_ids => []])
end
error:
Shop product print files shop products must exist
Shop product print files print locations must exist
params that pass:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"u/c103465uNCjF/trYrMleqxJ8b9wyLbU/vjPK4llYtCg/ODj92q5MN24==", "shop_product"=>{"sync"=>"1", "product_id"=>"3", "shop_product_print_files_attributes"=>{"print_file"=>"", "print_location_id"=>"6", "shop_product_id"=>"42"}, "store_product_id"=>"191234345", "store_variant_id"=>"15341234273", "id"=>"42"}, "commit"=>"Sync", "id"=>"42"}
The models haven't changed.
Print file in params still blank?
UPDATE 3:
**using this form: thanks to #arieljuod **
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title # get the print location from the association %>
<%= ff.hidden_field :print_location_id # save the print_location_id as a hidden field %>
<%= ff.file_field :print_file # file input %>
<% end %>
with this in the new and method housing the view:
#shop_product = ShopProduct.new
PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)}
works on create.
Issue still arises due to not knowing the ID of the ShopProduct until the page loads due to API and there is a possibility of being multiple IDs on one page.
I use:
<% if #shop_products.find_by(store_variant_id: variant.id) %>
<% shop_product = #shop_products.find_by(store_variant_id: variant.id) %>
<%= form_for shop_product do |f| %>
...
Which, variant comes from a loop defined by an API:
<% #in_store_variants.each do |variant| %>
Now when using shop_products (from when shop_product already exists from finding by the variant.id), the fields_for won't appear. Assuming this is because no records exist in relation. Only if a shop_product.shop_product_print_files exist, will they appear.
The only work around, at this time to my knowledge, is to save all print_locations but use a boolean for which are actually active, or search for which print_locations have an ID attached. But i would rather not do it that way and just save which print_locations are chosen on create (chosen by uploading a print_file).
To "fix" this issue, I:
added accepts_nested_attributes_for reject_if: proc { |attributes| attributes['print_file'].blank? } which doesn't save ShopProductPrintFile's unless the print_file field is submitted with something...
use this form (2 forms depending on if exists or not)
<% if #shop_products.find_by(store_variant_id: variant.id) %>
<%= form_for shop_product do |f| %>
<% PrintLocation.all.each{|p| shop_product.shop_product_print_files.build(print_location: p)} %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title %>
<%= ff.hidden_field :print_location_id %>
<%= ff.text_field :print_file %>
<% end %>
<%= f.submit "Sync" %>
<% end %>
<% else %>
<%= form_for #shop_product do |f| %>
<% PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)} %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title %>
<%= ff.hidden_field :print_location_id %>
<%= ff.text_field :print_file %>
<% end %>
...
The issue with 2 is i have have PrintLocation 1,2,3 associated, it will show 9 fields, the 1,2,3 ready for update, and the 6 ready for create.
is it possible to call the PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)} on already created ShopProducts's for where a shop_product_print_file doesn't exist in relation to the possible print location.
So for example...
Created ShopProduct with print location, 1,2,3 (out of 6 possible)
Now, shop_product_print_location where print_location exists will show for updating in the form, so thats 1,2, and 3. How can I have it so the other 3 that weren't created now show to update the ShopProduct and create new ShopProductPrintFile's? so it is possible to update the ShopProduct to have more print_locations to the shop_product_print_file model.
I have a nested fields_for text_field not appearing, I am not sure
what I have been done wrong.
You should add this line in your create action
#shop_product = ShopProduct.new(shop_product_params)
#shop_product.shop_product_print_files.build #this one
Also change shop_product_print_file_attributes to shop_product_print_files_attributes to avoid any further errors.
You have to tell rails which PrintLocation to use on each iteration since your object does not have any
<%= f.fields_for :shop_product_print_files, print_location do |a| %>
I'm not really sure if that's what you want, but the field will appear.
EDIT: so, I think you need something like this:
On the controller
#shop_product = something_to_get_the_product
PrintLocation.all.each{|p| #shop_product.shop_product_print_files.build(print_location: p)}
I prefer to do this here, I don't like that logic on the view
Now you have all the possible print location prebuilt on the shop product object
On the form
# note here the multipart option to allow files
<%= form_for #shop_product, multipart: true do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title # get the print location from the association %>
<%= ff.hidden_field :print_location_id # save the print_location_id as a hidden field %>
<%= ff.file_field :print_file # file input %>
<% end %>
<%= f.submit %>
<% end %>

Rails check_box_tag within form_for

Is it possible to pass the value of checked check_box_tags within a form_for in Rails inside a hash?
Here is a very generic, basic version of the form:
<%= form_for(:object, :url => { :action => 'create', :id => params[:id]}) do |f| %>
<p>Field_a: <%= f.text_field(:field_a) %></p>
<p>Field_b: <%= f.text_field(:field_b) %></p>
<p>Field_c: <%= f.text_field(:field_c) %></p>
<p>Check boxes:</p>
<% check_box_choices_object_array.each do |s| %>
<%= check_box_tag(s.name, 1, false) %>
<%= .name %><br />
<% end %>
<%= submit_tag("Create") %>
<% end %>
Outputs roughly:
Field_a ___________________
Field_b ___________________
Field_c ___________________
Check boxes:
[] box_a
[] box_b
[] box_c
[] box_d
[] box_e
[] box_f
[] box_g
My problem is that since the available check boxes aren't actual fields in the object's table in the database (i.e. I'm not using check_box(:field) in the form), each checked check box gets passed as an individual parameter (i.e. "box_a" => "1", "box_b" => "1", "box_e" => "1"). I would like them to be passed as such:
:checked_boxes => {"box_a" => "1", "box_b" => "1", "box_e" => "1"}
This way, I can access them easily with params[:checked_boxes].
How do I do this, or, better yet, is there a better solution (I'm new to rails)?
I think you'd get the results you want if you wrap the checkboxes iterator in a fields_for :checked_boxes tag - or at least get you close to the results you want.
<%= form_for(:object, :url => { :action => 'create', :id => params[:id]}) do |f| %>
<p>Field_a: <%= f.text_field(:field_a) %></p>
<p>Field_b: <%= f.text_field(:field_b) %></p>
<p>Field_c: <%= f.text_field(:field_c) %></p>
<p>Check boxes:</p>
<%= f.fields_for :checked_boxes do |cb| %>
<% check_box_choices_object_array.each do |s| %>
<%= cb.check_box(s.name, 1, false) %>
<%= .name %><br />
<% end %>
<% end %>
<%= submit_tag("Create") %>
<% end %>
you can deal with no database attributes and models using attr_accessor
class Thing < ActiveRecord::Base
attr_accessible :name
attr_accessor :box_a, :box_b, :box_c
end
This way you can call these attributes in your form.

Getting "builder", "parent_builder", and "namespace" fields returned from Postgresql/Hstore in rails

I built a simple application, which i am trying to update with a form, but got some weirdness going on.
I have a column 'field_items' which is an hStore. If i call debug on the model in my view...
<%= debug #app.field_items %>
I get the two proper items returned. In the rails console i also do not see the three extras.
I have a form_for where i iterate over the 'field_items'
In my form it returns three extra fields "builder", "parent_builder", and "namespace"
Anyone have any ideas? I have noticed if i comment out the serialize line on :field_items in the model, it doesn't return the three extra attributes
Here is my model
class App < ActiveRecord::Base
belongs_to :page
attr_accessible :content, :title, :layouts, :field_items
serialize :layouts, ActiveRecord::Coders::Hstore
serialize :field_items, ActiveRecord::Coders::Hstore
end
Here is the form/code from my edit view
<%= form_for [:admin, #app], :html => { :class => "form app_fields_form" } do |f| -%>
<div id="app_fields_row_container">
<%= f.fields_for :field_items, #app.field_items do |fi| %>
<% #app.field_items.try(:each) do |key, value| %>
<div class='app_fields_row item_row'>
<div class="column col1"><%= text_field_tag key, key, :class => "form_text_field dynamic_attr" %></div>
<div class="column col2"><%= fi.select key, options_for_select(APP_FIELD_TYPES, value), {}, {:class => "form_select"} %></div>
<div class="column col3">x</div>
</div>
<% end %>
<%- end -%>
</div>
<% end -%>
I was having the same issue. Removing #app.field_items from fields_for worked for me
<%= f.fields_for :field_items do |fi| %>
Remove the following line and it should work fine:
<%= f.fields_for :field_items, #app.field_items do |fi| %>
I had the exact same problem, and removing the fields_for call solved the issue, as there is no need for it as it isn't a nested resource.

Multiple forms for the same model in a single page

On the front page of my rap lyrics explanation site, there's a place where users can try explaining a challenging line:
alt text http://dl.dropbox.com/u/2792776/screenshots/2010-02-06_1620.png
Here's the partial I use to generate this:
<div class="stand_alone annotation" data-id="<%= annotation.id %>">
<%= song_link(annotation.song, :class => :title) %>
<span class="needs_exegesis"><%= annotation.referent.strip.gsub(/\n/, "\n <br />") %></span>
<% form_for Feedback.new(:annotation_id => annotation.id, :created_by_id => current_user.try(:id), :email_address => current_user.try(:email)), :url => feedback_index_path, :live_validations => true do |f| %>
<%= f.hidden_field :annotation_id %>
<%= f.hidden_field :created_by_id %>
<p style="margin-top: 1em">
<%= f.text_area :body, :rows => 4, :style => 'width:96%', :example_text => "Enter your explanation" %>
</p>
<p>
<% if current_user %>
<%= f.hidden_field :email_address %>
<% else %>
<%= f.text_field :email_address, :example_text => "Your email address" %>
<% end %>
<%= f.submit "Submit", :class => :button, :style => 'margin-left: .1em;' %>
</p>
<% end %>
</div>
However, putting more than one of these on a single page is problematic because Rails automatically gives each form an ID of new_feedback, and each field an ID like feedback_body (leading to name collisions)
Obviously I could add something like :id => '' to the form and all its fields, but this seems a tad repetitive. What's the best way to do this?
If you don't want to change your input names or your model structure, you can use the id option to make your form ID unique and the namespace option to make your input IDs unique:
<%= form_for Feedback.new(...),
id: "annotation_#{annotation.id}_feedback"
namespace: "annotation_#{annotation.id}" do |f| %>
That way our form ID is unique, i.e. annotation_2_feedback and this will also add a prefix, e.g. annotation_2_, to every input created through f.
Did you consider nested_attributes for rails models? Instead of having multiple new feedback forms where each is tied to an annotation, you could have multiple edit annotation forms where each annotation includes fields for a new feedback. The id's of the generated forms would include the annotations id such as edit_annotation_16.
The annotation model would have a relationship to its feedbacks and will also accept nested attributes for them.
class Annotation < ActiveRecord::Base
has_many :feedbacks
accepts_nested_attributes_for :feedbacks
end
class Feedback < ActiveRecord::Base
belongs_to :annotation
end
You could then add as many forms as you want, one for each annotation. For example, this is what I tried:
<% form_for #a do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
<% form_for #b do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
And the quick and dirty controller for the above edit view:
class AnnotationsController < ApplicationController
def edit
#a = Annotation.find(1)
#a.feedbacks.build
#b = Annotation.find(2)
#b.feedbacks.build
end
def update
#annotation = Annotation.find(params[:id])
#annotation.update_attributes(params[:annotation])
#annotation.save!
render :index
end
end
I had this same issue on a site I'm currently working on and went with the solution you mention at the bottom. It's not repetitive if you generate the ID programmatically and put the whole form in a partial. For example, on my site, I have multiple "entries" per page, each of which has two voting forms, one to vote up and one to vote down. The record ID for each entry is appended to the DOM ID of its vote forms to make it unique, like so (just shows the vote up button, the vote down button is similar):
<% form_for [entry, Vote.new], :html => { :id => 'new_up_vote_' + entry.id.to_s } do |f| -%>
<%= f.hidden_field :up_vote, :value => 1, :id => 'vote_up_vote_' + entry.id.to_s %>
<%= image_submit_tag('/images/icon_vote_up.png', :id => 'vote_up_vote_submit' + entry.id.to_s, :class => 'vote-button vote-up-button') %>
<% end -%>
I also had the same issue but wanted a more extensible solution than adding the ID to each field. Since we're already using the form_for ... |f| notation the trick is to change the name of the model and you get a new HTML ID prefix.
Using a variant of this method: http://www.dzone.com/snippets/create-classes-runtime (I removed the &block stuff)
I create a new model that is an exact copy of the model I want a second form for on the same page. Then use that new model to render the new form.
If the first form is using
#address = Address.new
then
create_class('AddressNew', Address)
#address_new = AddressNew.new
Your ID prefix will be 'address_new_' instead of 'address_' for the second form of the same model. When you read the form params you can create an Address model to put the values into.
For those stumbling here, looking for the solution for Rails 3.2 app, look at this question instead:
Rails: Using form_for multiple times (DOM ids)

Resources