Rails can't mass-assign protected attributes for id, created_at - ruby-on-rails

I'm using rails as the server side of a backbone.js site, therefore I'm constantly passing the rails objects back and forth.
I'm noticing errors in rails returning WARNING: Can't mass-assign protected attributes: id, created_at, updated_at.
Of course, I find this strange because I've never had to include these fields
My json looks fairly normal as far as I can tell
Parameters: {"id"=>1, "updated_at"=>"2011-04-21T16:41:02Z"}, "created_at"=>"2012-02-23T21:01:02Z", "action"=>"test update"}

There is nothing wrong with your JSON. The issue is one of security. Rails protects certain attributes by default from being created or updated from a giant hash. This is what the error is referring to when it uses the term "mass-assignment."
The JSON you posted:
Parameters: {"id"=>1, "updated_at"=>"2011-04-21T16:41:02Z"}, "created_at"=>"2012-02-23T21:01:02Z", "action"=>"test update"}
contains the id the created_at and the updated_at fields. When this JSON is passed into the action and the hash is used in a model_object.update_attributes(hash_fields) you will get this error. To avoid this error you can delete the fields from the hash and assign them later, or ideally, let ActiveRecord work it's magic for you and just ignore them.
If you really do need to assign them you can do that like:
model_object.id = id_variable
model_object.created_at = created_at_variable
model_object.updated_at = updated_at_variable
model_object.save
EDIT1 (to address the comment about passing back the id):
If you are using the Rails REST model and calling the controller/:id/action url, you don't need to pass the ID back, as that information is already embedded in the URL. It can be accessed via params[:id] and the hash via params[:model_name] (following the Rails model).
If you are doing something different and the ID must be in the JSON being passed back then you can simply do id = params[:model_name][:id].delete and that will delete the id from the hash and return the value in one call. It's not ideal, but it can get the job done in a pinch.

Those columns are protected by default for mass assignment and cannot be set manually. But, you can override this behavior by defining a method:
def self.attributes_protected_by_default
[] # ["created_at", "updated_at" ..other]
end
This will allow you to assign created_at and updated_at manually.

Related

Unpermitted parameters issue Ruby on Rails 5

I'm currently trying to understand how permitted parameters works in ruby.
Usually, in my_model.rb I have:
has_many: some_other_model
*
*
*
def my_model_params
params.require(:my_model).permit( :column1, some_other_model_attributes %i[other_column1])
etc...
and in the update function
my_object.update_attributes(my_model_params)
with a well formatted json which has some my_model root, and some_other_model_attributes as a child (array) with values.
My problem is I receive a json like this one
However the different arrays inside (such as codification, general_information) do contain attributes of the mission (general_information contains reference that is a column in the mission table) but there isn't any column named codification, or relation to a codification_attributes.
So, when I add :
general_information: %i[reference] in the permitted params, it says unknown attribute 'general_information' for Mission.
If not, no error are raised but in the log I can see unpermitted_parameter: general_information. And my object is not updated.
Finally if I reject it, there is no more unpermitted_parameter: general_information in the log but my object is not updated either.
I tried to set config.action_controller.action_on_unpermitted_parameters to false in my development config, it did nothing and it's probably a bad idea for production environment anyway.
The use of .permit! (even if it works) is currently not an option. And even though I think the json needs to be re-formatted it'd be better to find an other solution.
Thanks for the help.
unpermitted_parameter: ... in logs in not a problem which you need to fix, it's just an info.
How it works - you just permit needed parameters, you may think about them as a hash. Unpermitted parameters will not go into the model even if they are present in params. It means when you call
my_object.update_attributes(my_model_params)
it works like
my_object.update_attributes(column1: value_for_column1_from_params)
Keys in params should be named exactly as columns in the model, otherwise you need to prepare params somehow before create/update

Missing param even though I can see it IS there

This is my params as seem in the rails abort() screen:
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"ptXYHkAUh/uvK9blLdcPiarvCYKHJ1HWhqw+dByy7PQ=",
"account"=>{"name"=>"Hokuriku",
"amount"=>"0",
"is_default"=>"1"},
"commit"=>"Save",
"id"=>"5"}
See "is_default". But, when I do:
def update #accounts controller method
abort(account_params.inspect);
.
.
.. in the controller, it only shows:
{"name"=>"Hokuriku", "amount"=>"0"}
I can't see 'is_default'. Btw this column is also a newly added column. I have migrated though, and I can confirm the new column exists. Also, I've managed to output the value of that column to the the previous screen so I know that the model is handling it.
To fix it, I do the following abort:
abort(params[:account][:is_default].inspect); # outputs "1"
.. and now I can see it. So it does exist.
Any ideas what could cause this to happen? Ideally I want to handle it in the simplest cleanest way possible, as well as understand exactly what account_params is as it doesn't seem to be the same as params[:account:]. Thanks
I'd bet that it's the account_params method that does the filtering. Whereas in params[:account] you access raw unfiltered data.
Look at your account_params method. It contains a number of instructions to ignore passed params (for security reasons).
It most likely have a form:
params.require(:account).permit(:name, :amount)
require will raise an exception if params do not contain given key and returns matching hash. Permits silently removes all the keys not listed in arguments.
You can read more about strong attributes on github: https://github.com/rails/strong_parameters

Posting parent_id in Ember.js and Rails 4 causes unpermitted parameters error. Remove or ignore?

In ember.js (1.2) I am trying to POST a change to a child model but ember.js is including the parent_id in the POST. The parent_id is not a "permitted" parameter for my Rails 4 controller, however, so the POST fails with the following error.
Processing by ThingsController#update as JSON
Parameters: {"thing"=>{"title"=>"Test","location"=>"Baltimore","parent_id"=>nil}
Unpermitted parameters: parent_id
Since I don't want to make parent_id a permitted parameter for this model, how to I remove it from the POST?
I ran into the same thing with a createdAt attribute. It was easily solved by overriding the ApplicationSerializer (or in your case, ThingSerializer):
App.ApplicationSerializer = DS.ActiveModelSerializer.extend
serialize: (record, options) ->
json = #_super(record, options)
delete json.created_at
json
It won't cause you any problems to leave the value of parent_id in the POST parameters, so you can ignore it.
If you want to be clean about it (and not confuse other developers down the road), you should edit the form that is performing the POST and remove the form field that contains parent_id.
If you post your view code, I can held advise how to do the latter.
I was able to resolve this by removing the reference to parent in the child class. This turned out to not be necessary in my application since I often access children from the parent, but I never access the parent from the child. In other words, the new model looks something like:
App.Thing = DS.Model.extend({
// commented out -> parent: DS.belongsTo('App.ParentThing'),
title: DS.attr('String'),
location: DS.attr('String')
});

record = Member.find_by_user(params[:member][:user])

I found a sample code like the title of this post and have a question.
Why is this written like params[:member][:user]?
I got advice from my friend and he explained that [:member] means table name and [:user] means column name included in the table of member.
But I don't get it because table is always plural and it is obvious that he or she are trying to search in table members as showed Member.find.
It is written as params[:member][:user] because params is a nested hash, such as this:
params = {"utf8"=>"✓",
"authenticity_token"=>"uAbvJ/LE1f8eDcANe+TVip5nsWfP/xJxxoGmsQyKFnU=",
"access_token"=>"",
"member"=> {"name"=>"foobar",
"email"=>"foo#bar.com",
"user"=>"jimmy",
"session"=>"2013-01-17 13:15:00 UTC"},
"commit"=>"Submit",
"locale"=>"es"}
This means to get to the value of the user inside of member, you would need to write something like params[:member][:user]. This is typical behavior of forms submitted by Rails, as the model data will be in it's own hash as member is in this example.

rails using .send( ) with a serialized column to add element to hash

I'm serializing many attributes on a model Page as hashes.
Because of the high number of attributes, I've taken a meta-programming approach and want to use .send() to iterate through a collection of attributes (such that I don't have to type out an update action for each attribute.
I've done something like this:
insights.each do |ins|
self.send("#{ins.name}=", {(Time.now) => ins.values[1]['value'].to_f})
self.save
end
The problem is that this obviously overwrites the whole serialized column, whereas I wish to add this as an element to the serialized hash.
Tried something like this:
insights.each do |ins|
self.send("#{ins.name}[#{Time.now}]=", ins.values[1]['value'].to_f)
self.save
end
But get a NoMethodError: undefined method page_fan_adds_unique[Mon Aug 13 13:31:58 -0400 2012]=
In the console I'm able to do Page.find(5).page_fan_adds_unique[Time.now]= 12345 and save it as an additional element to the hash as expected.
So how can I use .send() to save an additional element to a serialized hash? Or is there some other approach? Such as using update_attribute or another method? Writing my own? Any help is appreciated, even if the advice is that I shouldn't be using serialization for this.
I'd do :
self.ins.name.send(:[]=, key, value)

Resources