multiple records in one form - strong params issue - ruby-on-rails

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

Related

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 issue with access of nested hash parameter

I have in rails the following form in a view
<%= form_for (#account) do |f| %>
<%= f.label :comments,"Comments" %>
<%=f.text_area :comments %>
<%= f.submit "Confirm",:name=>"conf" %>
<%= f.submit "Reject" %>
<% end %>
When I submit the form I get the following hash in the log before the update of the database
Started PATCH "/accounts/12" for 127.0.0.1 at 2015-08-13 21:31:18 +0200
Processing by UseractionsController#answer_with_comments as HTML
Parameters: {"utf8"=>"✓", "account"=>{"comments"=>"mycomments"}, "conf"=>"Confirm", "id"=>"12"}
I am trying to access the input in the comments text area in the controller. I tried
params[:account][:comments]
but it does not seem to work. Could anyone give me the appropriate syntax? Thanks.
EDIT
This is my controller code. Right now the if loop return false and nothing is added to the database even though there is something submitted ("mycomments" see above in the param nested hash)
if params[:bankaccount][:comments]
#bankaccount.update_attribute(:comments, params[:bankaccount][:comments])
end
It is only the appropriate syntax for your view. It assumes that you have content field on your Comment model.
<%= form_for (#account) do |f| %>
<%= f.label :comments,"Comments" %>
<%= f.fields_for :comments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.submit "Confirm",:name=>"conf" %>
<%= f.submit "Reject" %>
<% end %>
You also will have to declare nested attributes in your Account model and your params hash should be different.
You should watch these two Railscasts part 1 and part 2 to learn more about nested attributes.
Since you mention strong parameters as a tag you probably want to build this a bit differently.
private
def account_params
#the permit method might need to be altered depending on your model and view
params.require(:account).permit(:comments)
end
Somewhere else in your controller you would then do:
#bankaccount.update_attributes(account_params)
Please take a read: http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters

Rails 4 : Checkbox array is not updating my attribute

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

Ruby on Rails access whole params hash

I have an application that deals with user rotas - and I'm currently adding the ability for admin approvals. If the user updates their own rota the params hash looks something like:
Parameters: {id:1, role_id: 1, team_id:1, rota: [startDate: 01/01/2014, endDate:02/02/2014]}
and these are submitted using a form with:
<%= form_for [#team,#role,#rota] do |f| %>
form code
<% end %>
We need to access the attributes outside the rota: object but currently can't find a way to as:
params.require requires you to pass an object in.
My team members have decided to add hidden fields to submit the attributes within the rota object but that seems redundant seeing as they are quite clearly there, we just can't find a way to access them, and ideas?
I was talking about something like
def user_rota_params
params.require(:user)
.permit(:role_id, :team_id, :rota => [:startDate, :endDate])
end
Then, for your nested attributes you could use fields_for.
Your form, thus, should look something this (omitting labels and keeping it basic):
<%= form_for #user do |f| %>
<%= f.text_field :role_id %>
<%= f.text_field :team_id %>
<%= f.fields_for :rota do |ff| %>
<%= ff.date_field :startDate %>
<%= ff.date_field :endDate %>
<% end %>
<% end %>

Rails3: How to implement multiple checkboxes in a form?

What I have now gives me a dropdown menu where I can only select one:
<%= form_for(#submission) do |f| %>
<%= f.collection_select :id, Submission::SUB_ID, :to_s, :to_s %>
<% end %>
where SUB_ID=[1,2,3] in model Submission
I want to implement a checkbox instead of a dropdown menu so that I can select multiple SUB_ID (i.e. 1&2 or 1&3 or 2&3 or 1&2&3). I tried to use this but it does not work:
<%= f.check_box :id, Submission::SUB_ID, :to_s, :to_s %>
Any idea?
Try this:
# view
<%= form_for(#submission) do |f| %>
<%= Submission::SUB_ID.each do |sub_id| %>
<%= f.checkbox 'ids[]', value: sub_id, checked: #submission.id == sub_id %>
<%= sub_id %>
<% end %>
<% end %>
# controller
params[:submission][:ids].each do |checked_sub_id|
# do your logic here
end
you have to iterate over SUB_ID
somehow like this...
<% Submission::SUB_ID.each do |ssid| %>
<%= f.check_box "ids[]", value: ssid %>
<% end %>
or you can use formtastic gem. it has :as=>:check_boxes input fields http://www.ruby-doc.org/gems/docs/n/nuatt-formtastic-0.2.3/Formtastic/Inputs/CheckBoxesInput.html
The core answer is you need to loop over each item in Submission::SUB_ID and make a checkbox for each id. Depending on how your models are set up and what you want to do - you may need to be much more involved in the form building. I hesitate to provide specific examples without know more about how you want the data to come back to the controller
<%= form_for(#submission) do |f| %>
<% Submission::SUB_ID.each do sub_id %>
<%= f.check_box_tag 'submission_ids[]', sub_id %>
<% end %>
<% end %>
Note that that will not default anything to checked and it does not come back as part of the submission parameters.
Usually when I have a similar situation I'm using nested forms to add or remove objects.
If you're using Rails 4, there is a new helper, collection_check_boxes, which helps streamline the building of your check boxes.
<%= f.collection_check_boxes :submission_ids, Submission::SUB_ID, :to_s, :to_s %>
Documentation:
Form builder version - which wraps...
...the general form options helper
If you look at the documentation in the second link, you'll also find how to use the optional block syntax to customise the HTML structure for each check box.

Resources