I have a strange bug. I'm trying to hunt down the reason for things to break all of a sudden.
It has to do with the _destroy method on a child model when set true it raises an ActiveModel::MissingAttributeError where for some reason the true value has made itself into a parameter key instead of a value.
I understand this is what is causing the error, but not a clue as to why.
Below is the params hash that is getting posted that raises the error. It is the _destroy=true for the first photo in the collection.
{
"utf8"=>"✓",
"authenticity_token"=>"ek3R5OX0B0MFV7/ae88rLK4EqMbvQIpltMyBBmjsNlDONRFzLDMVeHdtrZ9wljQ/px7XPSP/9JdMWCg4M1OXLw==",
"simple_product"=>{
"vendor_id"=>"2",
"name"=>"The Regency Tea-Tray",
"vendor_code"=>"1105-082",
"photos_attributes"=>{
"0"=>{"sort_order"=>"0", "caption"=>"", "_destroy"=>"true", "id"=>"40"},
"1"=>{"sort_order"=>"1", "caption"=>"", "_destroy"=>"false", "id"=>"4639"},
"2"=>{"sort_order"=>"2", "caption"=>"", "_destroy"=>"false", "id"=>"4640"}},
}}, "commit"=>"Update Simple product", "id"=>"33"}
I'm a little stumped as to what in the logic is causing the true value to be set as a key. The code is very much boiler plate update action.
I solved this. Not quite sure what caused the change but it was actually a misconfigured counter_cache.
I am still not sure how this was setting the model attributes like it would, but after removing
has_many :photos, counter_cache: true
The error resolved.
Related
I want to get the order of validation messages to go in the same order as they do on our form.
We have three classes:
class User
accepts_nested_attributes_for :pledges
end
class Pledge
accepts_nested_attributes_for :companies
validates_presence_of :pledgor_surname
end
class Company
validates_presence_of :name
end
In one form, we potentially have to take attributes for all three, so we get params like the following:
{"pledges_attributes"=>
{"0"=>
{"pledgor_surname"=>"",
"id"=>"230",
"companies_attributes"=>
{"0"=>
{"id"=>"125",
"name"=>""
}
}
}
}
}
When I call #user.update(params), it fails validation as I'd expect. But the errors#full_messages list looks like this:
["Company name can't be blank", "Pledgor surname can't be blank"]`
And the errors appear on the page in the same order.
Short of hacking the messages object, is there a way to tell Rails which order to place the messages in, or at least which of pledgor errors and company errors should go first?
No, they are returned in a hash and hashes do not provide reliable ordering. This could be overridden, which is typically done by adding files to the lib folder and specifying your overrides in the rails config.
Edit According to the comment below since Ruby 1.9.3, hashes are actually ordered, so ignore what I said.
I'm trying to update a User record with the list of associated Addresses, connected through the has and belongs to many association.
The request body I'm sending through javascript is:
{"id":10,"name":"John Smith", "address_ids":[4,8]}
This is what I'm getting in the Rails server log:
Started PUT "/api/users/10" for 127.0.0.1 at 2013-10-03 16:30:43 +0200
Processing by Api::UsersController#update as HTML
Parameters:
{"id"=>"10", "name"=>"John Smith", "address_ids"=>[4, 8],
"user"=>{"id"=>"10", "name"=>"John Smith"}}
The thing to notice above is the address_ids array is not making it into the user hash according to the log.
In the controller i'm using strong parameters and updating the User like so:
attributes = params.require(:user).permit(:id, :name, {:address_ids => []})
#user.update_attributes(attributes)
The problem is the attributes don't contain the address_ids at the point of update so the associated records are not getting updated.
Temporary Solution
I've worked around the issue manually assigning the address_ids key to the attributes after they come back from the strong parameters check like so:
attributes = params.require(:user).permit(:id, :name, {:address_ids => []})
if params.has_key?(:address_ids)
attributes[:address_ids] = params[:address_ids]
end
#user.update_attributes(attributes)
This works fine but doesn't seem to me this is how it is supposed to work?
Why are the adderss_ids not getting auto assigned? How can this be implemented in a clear way?
If you want the addresses saved with the User you need to ensure your client side code sends the address_ids parameters as follows:
Parameters: {"user"=>{"id"=>"10", "name"=>"John Smith", "address_ids"=>["4", "8"]}}
The input field name attribute will be something like:
<input name="user[address_ids][]">
I also noticed you seem to have your user attributes repeated in your parameters so you may want to carefully review how your client code is generating the request.
I have a form in rails app that allows to update singular_collection_ids attribute (relation type is has_many through). And I also need to validate it before update.
The problem that is validation requires previous value of object, but there is no method singular_collection_ids_was to provide this value. Also singular_collection_ids method works directly with join table with no temporary values, so
self.class.find(id).singular_collection_ids
inside validation did not help.
Is there any way to get previous value in stage of validation?
Not sure it works (and it's definitely quirky) but you could try this :
class Player
has_many :join_models, before_remove: :prevent_achievement_removal
def prevent_achievement_removal( join_model )
errors.add( :base, "Cannot remove an achievement this way !" )
raise ::ActiveRecord::RecordInvalid
end
end
according to the doc, if the callback raises any error, the record is not removed. However, something does not really feel right about this... I'll try to think about a better solution.
I think Rails is interpreting my hash literal as a nested attribute. I would actually just like to convert the hash to a string. It's not a nested attribute related to some model, nor should it be related to a model. Here, nesting the values is merely a convenient way to pass data through a form without too much busywork.
:params was
{"utf8"=>"✓",
"authenticity_token"=>"[deleted for SO]",
"scorecard"=>{"1"=>"2",
"2"=>"2",
"3"=>"2",
"4"=>"2",
"5"=>"2",
"6"=>"2",
"7"=>"2",
"8"=>"2",
"9"=>"2",
"10"=>"2",
"11"=>"2",
"12"=>"2"}},
"commit"=>"Create Assessment"}
.. but when I do:
assessment = Assessment.new(params[:assessment])
...
#assessment.save
... I get:
unknown attribute: scorecard
Interestingly, when
attr_accessible :scorecards
.. is in the model (note plural form), I get:
Can't mass-assign protected attributes: scorecard
Since when attr_accessible is what I actually want (:scorecard), I get "unknown attribute: scorecard", it seems Rails thinks it's dealing with a nested attribute.
Can I tell Rails not to treat :scorecard as a nested attribute?
Thanks.
did you mean to leave out the 's' in attr_accessible?
attr_accessible :scorecard(s)
EDIT
you many be getting this next error because you are missing a scorecard column on your table
unknown attribute: scorecard
Can you post your column names of the table? It seems that you are missing the column 'scorecard'. Or did you name it as 'scorecards' ? Just in case.
Also you have to declare serialized :scorecard to save a serialized value. Saving it as a JSON is also an option. Just need additional parsing.
In Rails 2.3.5 model I am using
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }
But its rejecting if there is the textbox is not empty..
How can I make it to reject only if there is nothing entered into the textbox ?
Are you confusing reject_if with record validation? The reject_if merely tells the app to ignore that set of nested attributes if a condition is true. In your case, the question's attributes will be ignored if the question's content is blank. If you want to validate or otherwise ensure that the question record(s) have a non blank value for content, you'd put validation in your question model.
You also might consider changing lambda{} to proc{}.
reject if will save the parent object and any other amount of child objects rejecting only those that fail the reject_if condition. If this is what you want then it is fine, i suggest debugging a little bit, put in a print statement or something, maybe
lambda { |a| puts a.inspect; a[:content].blank? }
If you want the whole nested object to save all at once, then use validations.