'Unknown key(s)' ArgumentError - ruby-on-rails

I'm working on a moderating feature for my application, which is based on a basic scaffold structure. What I need, is to edit several records with the boolean parameter publised on false. In moderate.html I'm getting the list of all unpublished entries with the ability to change their parameters which, what and published. The error appears, when I'm trying to save the changes through the complete action.
ArgumentError in NamesController#complete
Unknown key(s): 7, 1, 4
The "7, 1, 4" are id of my unpublished records.
Here are the parts of my code:
#names_controller.rb
def moderate
#names = Name.find(:all, params[:name_ids], :conditions => {:published => false})
respond_to do |format|
format.html { render :action => "moderate" }
format.xml
end
end
def complete
#names = Name.find(params[:name_ids])
#names.each do |name|
name.update_attributes!(params[:name].reject { |k,v| v.blank? })
end
flash[:notice] = "Updated records!"
redirect_to names_path
end
#moderate.html.erb
<% form_tag complete_names_path do %>
<% #names.each do |name| %>
<fieldset>
<% fields_for "name_ids[#{name.id}]", name do |name_fields| %>
<%= name_fields.text_field :which %>
<%= name_fields.text_field :what %>
<%= name_fields.check_box :published %>
<% end %>
</fieldset>
<% end %>
<%= submit_tag "Ok" %>
<% end %>/
#routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect 'moderate', :controller => 'names', :action => 'moderate'
map.resources :names, :collection => { :complete => :put}
map.root :names
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
I understand, that there's something wrong with the name_ids, but don'nt understand, what should I do.
Thank you in advance.
ruby 1.8.7 (2009-06-12 patchlevel 174)
[universal-darwin10.0] Rails 2.3.5
Rails log for moderate and complete actions:
Processing NamesController#moderate (for 127.0.0.1 at 2010-10-16 21:36:42)
[GET] [4;35;1mName Load (0.6ms)[0m
[0mSELECT * FROM "names" WHERE
("names"."published" = 'f') [0m
Rendering template within
layouts/names Rendering names/moderate
Completed in 12ms (View: 7, DB: 1) |
200 OK [http://localhost/moderate]
Processing NamesController#complete
(for 127.0.0.1 at 2010-10-16 21:36:49)
[POST] Parameters: {"commit"=>"Ok",
"authenticity_token"=>"CtmsjIavksOMSIArrdovkkzuZzHVjkenFFMO5bHIvgg=",
"name_ids"=>{"7"=>{"published"=>"0",
"what"=>"Партия", "which"=>"Крутая"},
"1"=>{"published"=>"1",
"what"=>"Россия", "which"=>"Единая"},
"4"=>{"published"=>"0",
"what"=>"Организация",
"which"=>"Молдавская"}}}
[4;36;1mName Load (0.4ms)[0m
[0;1mSELECT * FROM "names" WHERE
("names"."id" IN (7,1,4)) [0m
NoMethodError (You have a nil object
when you didn't expect it! You might
have expected an instance of Array.
The error occurred while evaluating
nil.reject):
app/controllers/names_controller.rb:47:in
complete'
app/controllers/names_controller.rb:46:in
each'
app/controllers/names_controller.rb:46:in
`complete'
Rendered rescues/_trace (110.3ms)
Rendered rescues/_request_and_response
(0.5ms) Rendering rescues/layout
(internal_server_error)

Likely you need to get just the keys from the name_ids hash. Try:
#names = Name.find(params[:name_ids].keys)
A separate problem is your reference to params[:name], which is nil. Did you mean (EDIT: use to_s to match the params key, lol):
#names.each do |name|
name.update_attributes!(params[:name_ids][name.id.to_s].reject { |k,v| v.blank? })
end
EDIT (brief-ish explanation):
What was happening was that you had a nested hash in the params, params[:name_ids]. It looked like:
"name_ids"=>{"7"=>{"published"=>"0", "what"=>"Партия", "which"=>"Крутая"}, "1"=>{"published"=>"1", "what"=>"Россия", "which"=>"Единая"}, "4"=>{"published"=>"0", "what"=>"Организация", "which"=>"Молдавская"}}
An ActiveRecord 'find' method can take an array of ids, but not a hash of values. What you were originally submitting to 'find' in this line:
#names = Name.find(params[:name_ids])
...was the value for params[:name_ids]:
{"7"=>{"published"=>"0", "what"=>"Партия", "which"=>"Крутая"}, "1"=>{"published"=>"1",
"what"=>"Россия", "which"=>"Единая"}, "4"=>{"published"=>"0", "what"=>"Организация",
"which"=>"Молдавская"}
When what you wanted was:
#names = Name.find(['7','1','4'])
which is what calling params[:name_ids].keys gives you.
The second problem was this line:
name.update_attributes!(params[:name].reject { |k,v| v.blank? })
There was no value ':name' in params, so calling 'reject' on it cause the 'no method' error -- there is no 'reject' method on the nil object. What you wanted was to update the attributes for the 'name' that corresponded to the particular name in the loop. This meant you wanted to get the values out of params[:name_ids][:id] where :id was the id of 'name'.
It all goes back to the way fields_for created the params to begin with. This line:
<% fields_for "name_ids[#{name.id}]", name do |name_fields| %>
meant that params would contain a hash called 'name_ids', with keys corresponding to name.id, that themselves would contain hashes of attributes that ActiveRecord could use in the update_attributes method.
There's a good bit of the famous Rails magic to keep track of in there -- does that help?

Related

rails form with validation always failing

I'm stuck on this... Asked a few questions already, but can't get my head around this.
I have a form for adding bibliography (model Biblio) that has a simple validation field on title of the bibliography.
Validation always fails, even when valid data is added.
MODEL
class Biblio < ApplicationRecord
# validates_presence_of :auteurs => there's a nested form too but
# I commented it out in order to isolate the problem
accepts_nested_attributes_for :auteurs
validates :titre, presence: true
CONTROLLER
(full text and I didn't translate in order to avoid typos)
def new
#biblio = Biblio.new(params_biblio)
#biblio.auteurs.build
end
def nouveau
# this method renders 'nouveau.html.erb',
# that contains the form allowing the addition of bibliography
#biblio = Biblio.new
if params[:id] # id is an optional parameter
#auteur = Auteur.find(params[:id])
#idauteur = #auteur.id
end
end
def ajouter
# is the method that treats the post form that was sent
#biblio = Biblio.new
if #biblio.save
# the 4 following lines are irrelevant here as they only add the
# second and subsequent authors to the join table.
# No validation and works fine.
b = auteurs_devises(params[:biblio][:auteurs])
aut = b.map do |var|
lett = Auteur.find(var)
lett.biblios << #biblio
end
redirect_to voir_biblio_url(Biblio.last)
else
if params[:id]
#auteur = Auteur.find(params[:id])
#idauteur = #auteur.id
end
render 'nouveau'
end
end
THE VIEW:
<%= form_for :biblio, url: administration_ajoute_biblio_url do |f| %>
<%= f.fields_for :auteurs do |aut| %>
<%= aut.label t('auteur') %>
<%= aut.text_field :nom , :name =>"biblio[auteurs][nom]", data: {autocomplete_source: auteurs_enum_path} %>
<% end %>
<%= f.label t('titre').capitalize %>
<%= f.text_field :titre %>
These are the params that are sent to the method nouveau:
Started POST "/administration/biblios/nouveau" for ::1 at 2017-02-07 21:28:28 +0100
Processing by Administration::BibliosController#ajouter as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+354h4M0Tg+BX21XAuQ6YMKS0BGQ8UjET8paKjkGCBsS1up1lB131KsoaCy563X4juDz0EJy46WgXbHcu51Kgw==", "biblio"=>{"auteurs"=>{"nom"=>"Godding"}, "titre"=>"Test Tuesday Evening", "soustitre"=>"", "recueil"=>"", "editeur"=>"", "annee"=>"", "isbn"=>"", "genre"=>"source", "revue_id"=>"", "revue_no"=>"", "revue_page"=>"", "lieu"=>"", "commentaire"=>""}, "commit"=>"Enregistrer"}
(0.2ms) BEGIN
(0.1ms) ROLLBACK
Rendering administration/biblios/nouveau.html.erb within layouts/messources
CONTROLLER AGAIN
def params_biblio
params.require(:biblio).permit(
:titre,
:soustitre,
:editeur,
:isbn,
:recueil,
:genre,
:revue_id,
:revue_no,
:revue_page,
:annee,
:lieu,
:commentaire,
auteurs: [:nom] )
end
For the sake of completeness, here's my routes.rb:
# ADMINISTRATION => BIBLIOGRAPHIE
get 'biblios/nouveau(/:id)' => 'biblios#nouveau', as: 'nouvelle_biblio'
post 'biblios/nouveau(/:id)' => 'biblios#ajouter', as: 'ajoute_biblio'
delete 'biblios/supprime/:id' => 'biblios#supprime', as: 'supprime_biblio'
get 'biblios/maj/:id' => 'biblios#cherche_maj', as: 'maj_biblio'
patch 'biblios/maj/:id' => 'biblios#maj', as: 'patch_maj_biblio'
I must be blind. I'm doing something wrong... I put a title to this bibliography ('Test Tuesday Evening'), this is the only field on which I left a validation, and despite this, validation always fails.

Saving arrays in Rails 4.2.3

I am having some trouble saving arrays in Rails.
Rails version: 4.2.3 | Ruby version: 2.2.1 | DB: PostgreSQL
In my view, I have a collection of check boxes that shows the conferences that my member attended.
<%= f.fields_for :conferences_member do |conference| %>
<%= collection_check_boxes(:conferences_member, :conference_id, #all_conferences, :id, :name)%>
<% end %>
I put a break point (binding.pry) after the create action in my MembersController, and surprisingly, it shows the selected check boxes:
Processing by Database::MembersController#create as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"XYZ==",
[...] "conferences_member"=> {"conference_id"=>["3", "5", ""]}, [...]
Now, if I go to rails c, and type ConferencesMember.last to check what was saved, I get:
pry(main)> ConferencesMember.last
ConferencesMember Load (0.5ms) SELECT "conferences_members".* FROM
"conferences_members" ORDER BY "conferences_members"."id" DESC LIMIT 1
=> nil
These are my associations:
#=> member.rb
has_one :conferences_member
accepts_nested_attributes_for :conferences_member, allow_destroy: true, reject_if: :all_blank
#=> conferences_member.rb
serialize :conference_id, Array
belongs_to :member
#=> members_controller.rb
params.require(:member).permit( [...]
:conference_member_attributes => [:id, :member_id, :conference_id => []],
[...])
I want to thank you in advance. I've tried almost everything here on StackOverflow, but I don't see my error.
Thank you again.
EDIT:
More of my MembersController:
def new
#member = Member.new
#member.build_conferences_member
end
def create
#member = Member.new(member_params)
binding.pry
end
The log doesn't show any error, it just shows that conferences were not saved at all.
First, your field needs to be renamed to nest the :conference_id in :conferences_member_attributes (not in :conferences_member as you do now). Take advantage of the form object yielded by fields_for:
<%= f.fields_for :conferences_member do |conference| %>
<%= conference.collection_check_boxes :conference_id, #all_conferences, :id, :name %>
<% end %>
You also need to actually save the record in the create action: Member.new builds the record but does not save it. Typically, the create action branches based on whether the record saved or did not (due to validations). So you might rewrite this method like so:
def create
#member = Member.new(member_params)
# when #member.save returns true, it saved to the db successfully
if #member.save
redirect_to members_path, notice: "Member #{#member.id} saved!"
# otherwise, it didn't save because of a validation error, so we render the error
# to the user and give them a chance to fix it
else
flash[:error] = "Member didn't save: #{#member.errors.full_messages.to_sentence}"
render :new
end
end
Lastly, to make sure your data gets through your strong parameters, check your logs for any messages that parameters were filtered out. The messages look like:
Unpermitted parameters: your_favorite_attribute

Rails form to create model with reference

I'm trying to create a model form that will allow me to add references by name via a <select> tag.
e.g.
In the database there are already RefModels with ref_name = "a", ref_name = "b", ref_name = "c".
Form:
<%= form_for #model %>
<%= f.label :ref_models, "Referenced Models:" %>
<%= f.select :ref_models, ["a","b","c"], {}, {:multiple => true} %>
Controller:
def create
#model = Model.new(model_params)
params[:model][:ref_models].each do |ref|
#ref = RefModel.where("ref_name = ?", ref)
#model.ref_models << #ref
end
respond_to do |format|
...
end
end
In my logs I'm getting this error:
Started POST "/models" for 127.0.0.1 at 2013-06-25 16:20:48 -0300
Processing by ModelssController#create as JS
Parameters: {"utf8"=>"✓", "models"=>{..., "ref_models"=>["a", "b", "c"], ...}, "commit"=>"Create"}
Completed 500 Internal Server Error in 2ms
ActiveRecord::AssociationTypeMismatch (RefModel(#70144007274440) expected, got String(#70144005442620)):
app/controllers/models_controller.rb:52:in `create'
What's the problem?
Note:
I changed the actual model names for "model" and "ref_model" to generalize.
EDIT:
The error occurs on this line:
#model = Model.new(model_params)
The error comes from this part:
#model = Model.new(model_params)
I'm pretty sure your model_params is actually params[:model]. If yes, it means it tries to create a record for Model with the attribute ref_models and the values contained in params[:model][:ref_models]
You should take off the params[:model][:ref_models] before passing it to the Model.new(params[:model]):
def create
ref_models = params[:model].delete(:ref_models)
#model = Model.new(params[:model])
ref_models.each do |ref|
#ref = RefModel.where("ref_name = ?", ref)
#model.ref_models << #ref
end
respond_to do |format|
...
end
end
Do these ref_models have IDs as well as names? If so, you can get Rails to do the heavy lifting for you.
In your view, do:
<%= f.select :ref_model_ids, RefModel.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>
Then, you can take out the custom code from your controller, Rails knows how to link those models up appropriately. That's assuming you have a table like model_ref_model with columns for model_id and ref_model_id.
Hope that helps...

Rails 4.0 Strong Parameters nested attributes with a key that points to a hash

I was playing around with Rails 4.x beta and trying to get nested attributes working with carrierwave. Not sure if what I'm doing is the right direction. After searching around, and then eventually looking at the rails source and strong parameters I found the below notes.
# Note that if you use +permit+ in a key that points to a hash,
# it won't allow all the hash. You also need to specify which
# attributes inside the hash should be whitelisted.
https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/strong_parameters.rb
So its saying you have to specify every single every single attribute within the has, I tried the following:
Param's example:
{"utf8"=>"✓",
"authenticity_token"=>"Tm54+v9DYdBtWJ7qPERWzdEBkWnDQfuAQrfT9UE8VD=",
"screenshot"=>{
"title"=>"afs",
"assets_attributes"=>{
"0"=>{
"filename"=>#<ActionDispatch::Http::UploadedFile:0x00000004edbe40
#tempfile=#<File:/tmp/RackMultipart20130123-18328-navggd>,
#original_filename="EK000005.JPG",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"screenshot[assets_attributes][0][filename]\"; filename=\"EK000005.JPG\"\r\nContent-Type: image/jpeg\r\n">
}
}
},
"commit"=>"Create Screenshot"}
Controller
def screenshot_params
params.require(:screenshot).permit(:title,
:assets_attributes => [:filename => [:#tempfile,:#original_filename,:#content_type,:#headers]
The above isn't "working" (its not triggering carrierwave) however I am no longer getting errors (Unpermitted parameters: filename) when using the standard nested examples I found ex:
def screenshot_params
params.require(:screenshot).permit(:title, assets_attributes: :filename)
If anyone could help it would be great. I was not able to find a example with nested with a key that points to a hash.
My other answer was mostly wrong - new answer.
in your params hash, :filename is not associated with another hash, it is associated with an ActiveDispatch::Http::UploadedFile object. Your last code line:
def screenshot_params
params.require(:screenshot).permit(:title, assets_attributes: :filename)
is actually correct, however, the filename attribute is not being allowed since it is not one of the permitted scalar types. If you open up a console, and initialize a params object in this shape:
params = ActionController::Parameters.new screenshot: { title: "afa", assets_attributes: {"0" => {filename: 'a string'}}}
and then run it against your last line:
p = params.require(:screenshot).permit(:title, assets_attributes: :filename)
# => {"title" => "afa", "assets_attributes"=>{"0"=>{"filename"=>"abc"}}}
However, if you do the same against a params hash with the uploaded file, you get
upload = ActionDispatch::Http::UplaodedFile.new tempfile: StringIO.new("abc"), filename: "abc"
params = ActionController::Parameters.new screenshot: { title: "afa", assets_attributes: {"0" => {filename: upload}}}
p = params.require(:screenshot).permit(:title, assets_attributes: :filename)
# => {"title" => "afa", "assets_attributes"=>{"0"=>{}}}
So, it is probably worth a bug or pull request to Rails, and in the meantime, you will have to directly access the filename parameter using the raw params object:
params[:screenshot][:assets_attributes]["0"][:filename]
So, you're dealing with has_many forms and strong parameters.
This is the part of the params hash that matters:
"assets_attributes"=>{
"0"=>{
"filename"=>#<ActionDispatch::Http::UploadedFile:0x00000004edbe40
#tempfile=#<File:/tmp/RackMultipart20130123-18328-navggd>,
#original_filename="EK000005.JPG",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"screenshot[assets_attributes][0][filename]\"; filename=\"EK000005.JPG\"\r\nContent-Type: image/jpeg\r\n">
}
}
when you define strong parameters like this...
permit(:assets_attributes => [:filename])
Things break, because where rails expects a filename it's getting this "0"
What does that number mean? It's the id for the asset you are submitting via your form. Now initially you might think you have to do something like
permit(:assets_attributes => [:id => [:filename]])
This looks like it follows other strong parameters syntax conventions. However, for better or for worse, they have made things a little easier, and all you have to write is:
permit(:assets_attributes => [:asset_id, :filename])
Edit -
As jpwynn pointed out in the comments, in Rails 4.2.4+ the correct syntax is
permit(:assets_attributes => [:id, :filename])
and that should work.
When you hit walls with strong params, the best thing to do is throw a debugger in your controller and test things out. params.require(:something).permit(:other_things) is just a method chain so you can try out different things on the full params hash until you find what works.
try
def screenshot_params
params.require(:screenshot).permit(:title, :assets_attributes => [:filename, :id, :screenshot_id])
end
I had this issue about a month ago and some searching around dug up this solution. It was adding the :id or :screenshot_id that fixed the problem (or both, I can't remember). This works in my code though.
Actually there is a way to just white-list all nested parameters.
params.require(:screenshot).permit(:title).tap do |whitelisted|
whitelisted[:assets_attributes ] = params[:screenshot][:assets_attributes ]
end
This method has advantage over other solutions. It allows to permit deep-nested parameters.
While other solutions like:
params.require(:screenshot).permit(:title, :assets_attributes => [:filename, :id, :screenshot_id])
Don't.
Source:
https://github.com/rails/rails/issues/9454#issuecomment-14167664
I had same problem just got it fixed now all you have to do is
params.require(:vehicle).permit(:user_id, assets_attributes: [:id, :image]).
Use pry gem to see what kind of attributes your asset object has makes sure theres an id and add other missing attribute, that should then work perfectly.
Am using paperclip assets is my nested object inside the vehicle class and an attachment of images is added to the asset.
make sure you do the validation in the model
accepts_nested_attributes_for :assets, allow_destroy: true
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
In your view loop through the asset to get each image
<%= #vehicle.assets.size %>
<% for asset in #vehicle.assets %>
<%=link_to image_tag (asset.image.url(:thumb)) %>
<% end %>
If am correct your problem is that asset_attributes is an array with each image having an index column and an image
Your form_for should have something similar to this and if you want you can also include preview so the upload can view their images use the bottom code for that
<div class="field">
<h3>Vehicle Image Upload</h3>
<%= f.fields_for :assets do |asset_fields| %>
<% if asset_fields.object.new_record? %>
<p>
<%= asset_fields.file_field :image %>
</p>
<% end %>
<% end %>
</div>
<div class="field">
<h4>Vehicle Image</h4>
<%= f.fields_for :assets do |asset_fields| %>
<% unless asset_fields.object.new_record? %>
<%= link_to image_tag(asset_fields.object.image.url(:thumb)),
asset_fields.object.image.url(:original)%>
<%= asset_fields.check_box :_destroy %>
<% end %>
<% end %>
</div>
Sanitize before save in controller Sanitize accepts_nested_attributes_for attributes with index.
before_action :sanitize_fields_params, :only => [:create, :update]
def sanitize_fields_params
product_free_shippings_attributes = params[:product][:product_free_shippings_attributes]
product_free_shippings_attributes.each do |index, key_value|
params[:product][:product_free_shippings_attributes]["#{index}"][:weight] = clear_decimal(key_value[:weight])
params[:product][:product_free_shippings_attributes]["#{index}"][:height] = clear_decimal(key_value[:height])
params[:product][:product_free_shippings_attributes]["#{index}"][:width] = clear_decimal(key_value[:width])
params[:product][:product_free_shippings_attributes]["#{index}"][:depth] = clear_decimal(key_value[:depth])
end
end
def clear_decimal(field)
return (field.to_s.gsub(/[^\d]/, '').to_d / 100.to_d) unless field.blank?
end

Create a model attribute from checkbox input

This creates and saves a new row to my database. The problem is that I have a checkbox in my view file for a boolean value, but no matter whether the box is checked or not, the new row is always false. I also can't get any of the other attributes to show up as anything other than nil. Any ideas?
This is my view is:
<%= form_for(#setting) do |s| %>
<div class="field" >
<%= s.label :my_setting_attribute %>
<%= s.check_box(:my_setting_attribute) %>
</div>
<div class="actions">
<%= s.submit "Submit" %>
</div>
<% end %>
And my controller:
def new
#setting = Setting.new
end
def create
#setting = Setting.new(params[:setting])
if #setting.save
redirect_to :action => 'index', :id => #setting.id
else
redirect_to :action => 'error'
end
end
I think I have my route file set correctly:
resources :settings do
collection do
get :index
post 'settings/new'
get 'settings/show'
end
end
Here's the development log excerpt:
Started POST "/settings" for 10.7.94.191 at 2011-07-25 20:30:11 -0400
Processing by SettingsController#create as HTML
Parameters: {"utf8"=>"â", "authenticity_token"=>"xxxxxxxxxxx=", "setting"=>{"my_setting_attribute"=>"1", "other_setting_attribute"=>"hello"}, "commit"=>"Submit"}
ESC[1mESC[36mUser Load (0.1ms)ESC[0m ESC[1mSELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1ESC[0m
ESC[1mESC[35mSQL (0.1ms)ESC[0m BEGIN
ESC[1mESC[36mSQL (1.0ms)ESC[0m ESC[1mdescribe `settings`ESC[0m
ESC[1mESC[35mAREL (0.3ms)ESC[0m INSERT INTO `settings` (`facebook_token`, `twitter`, `user_id`, `created_at`, `updated_at`, `image_id`) VALUES (NULL, NULL, NULL, '2011-07-26 00:30:12', '2011-07-26 00:30:12', NULL)
ESC[1mESC[36mSQL (57.1ms)ESC[0m ESC[1mCOMMITESC[0m
Redirected to http://3000/settings?id=12
May not be the "correct" way of doing it but you could probably do this:
In your controller for the update and create method, check what the value of params[:my_setting_attribute] is. You may need to conditionally set the attribute to either "true" or "false".
e.g. perhaps it is returning "1" instead of true (which your log seems to indicuate), then you can set it yourself with something like #my_object.my_setting_attribute = true if params[:my_setting_attribute] == "1". Then probably add an else clause to set everything else to false, just to be safe.

Resources