Missing param even though I can see it IS there - ruby-on-rails

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

Related

How to handle Rails params that may or may not exist, in controller action?

What is best practise for making a controller action that sometimes has key/values in params and sometimes doesn't?
I'm using a single endpoint for 2 similar purposes.
return items to be displayed without filters applied. No search terms from frontend.
return items to be displayed with filters applied. Yes search terms from frontend.
Example of former:
<ActionController::Parameters {"controller"=>"api/v1/items", "action"=>"search"} permitted: false>
Example of latter:
<ActionController::Parameters {"item"=>{"location"=>"New York"}, "controller"=>"api/v1/items", "action"=>"search"} permitted: false>
But it feels weird to detect whether search terms exist or not. I can do this:
if params.key?(:item)
...
end
But I started to wonder if combining 2 different pieces of logic in the same controller is an anti-pattern.
Additionally, strong params will just break.
if item_params
...
def item_params
params.require(:item).permit(:location)
end
#=> param is missing or the value is empty: item
That said, combining them into a single endpoint simplifies the frontend logic. But I'm not sure if it's against convention.
Are there any best practises for this kind of situation?
Use fetch(:key_name, default_value)[1] instead of require if the parameter is optional. For example:
def item_params
params.fetch(:item, {}).permit(:location)
end
Using an empty hash as the default guarentees that you have an ActionController::Parameters instance present which is useful since it you can use accessors and #dig without getting nil errors:
if item_params[:location].present?
# ...
end
Use empty? and any? to detect if the parameter is in fact empty. Having a single endpoint is not in itself a bad solution. The problem usually comes when you cram everything into the controller itself. Filters can be split into seperate objects such as models to move the logic to somewhere where it can be more easily tested.

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

How does Rails know the difference between these two identical expressions?

I am using a 4-year old Rails tutorial and I have Rails 4.0.2. I made a model called "Thing" and a controller called "Things". The "Thing" model has one attribute called "data". In my create action, I had this line:
#thing = Thing.new(params[:thing])
which results in this error:
ActiveModel::ForbiddenAttributesError in ThingsController#create
I found a StackOverflow thread that said I needed to require my needed parameters, and that worked just fine.
Before I looked that up I tried putting the hash from my params directly into the Thing.new() method and I didn't get an error. I started with this line:
puts params[:thing]
in my create action, typed "12345" in my text field, hit submit and got this in the console:
{"data"=>"12345"}
So I tried this in the create action:
#thing = Thing.new({"data" => "12345"})
and I didn't get the error. I even confirmed they were identical by doing this:
puts params[:thing] == {"data"=>"12345"}
and I get "true" on the console. So,
Thing.new(params[:thing])
gives me the error, but
Thing.new({"data"=>"12345"})
does not.
How can Rails tell the difference between these two arguments when they seem to be identical?
params[:thing] is not the same thing as {"data" => "12345"}, they just have the same value when inspect is called on them, and params's class overrides == to say it's equal to the hash.
Rails 4+ uses Strong Parameters, which is a security feature to make sure you know what you're putting in your models. Basically, Rails wants to you check the validity of the parameters. It lets you do Thing.new({"data" => "12345"}) because you, the developer, are creating the Hash directly, and are more trustworthy than someone on the internet calling your server.

How to retrieve all attributes from params without using a nested hash?

I am currently in the process of making my first iphone app with a friend of mine. He is coding the front end while I am doing the back end in Rails. The thing is now that he is trying to send necessary attributes to me with a post request but without the use of a nested hash, which means that that all attributes will be directly put in params and not in a "subhash". So more specifically what I want to do is be able to retrieve all these attributes with perhaps some params method. I know that params by default contains other info which for me is not relevant such as params[:controller] etc.. I have named all attributes the same as the model attributes so I think it should be possible to pass them along easily, at least this was possible in php so I kind of hope that Rails has an easy way to do it as well.
So for example instead of using User.new(params[:user]) in the controller I have the user attributes not in the nested hash params[:user] but in params directly, so how can I get all of them at once? and put them inside User.new()?
I found the solution to my problem. I had missed to add the attr_accessible to my model which was what initially returned the error when I tried to run code like: User.new(params) having been passed multiple attributes with the post request.
The solution was very simple, maybe too simple, but since this is my first real application in Rails I feel that it might be helpful for other newbies with similar problems.
If you would like to just pass a more limited version of params to new, you should be able to do something like the following:
params = { item1: 'value1', item2: 'value2', item3: 'value3' }
params.delete(:item2)
params # will now be {:item1=>"value1", :item3=>"value3"}
Also see this for an explanation of using except that you mention in your comment. An example of except is something like:
params.except(:ssn, :controller, :action, :middle_name)
You can fetch the available attributes from a newly created object with attribute_names method. So in this special example:
u = User.create
u.attributes = params.reject { |key,value| !u.attribute_names.include?(key)
u.save

Nested model error messages

I am using Ruby on Rails 3.0.9 and I am trying to validate a nested model. Supposing that I run validation for the "main" model and that generates some errors for the nested model I get the following:
#user.valid?
#user.errors.inspect
# => {:"account.firstname"=>["is too short", "can not be blank"], :"account.lastname"=>["is too short", "can not be blank"], :account=>["is invalid"]}
How you can see the RoR framework creates an errors hash having following keys: account.firstname, account.lastname, account. Since I would like to display error messages on the front-end content by handling those error key\value pairs with JavaScript (BTW: I use jQuery) that involves CSS properties I thought to "prepare" that data and to change those keys to account_firstname, account_lastname, account (note: I substitute the . with the _ character).
How can I change key values from, for example, account.firstname to account_firstname?
And, mostly important, how I should handle this situation? Is what I am trying to do a "good" way to handle nested model errors? If no, what is the common\best approach to do that?
I've made a quick Concern which shows full error messages for nested models:
https://gist.github.com/4710856
#1.9.3-p362 :008 > s.all_full_error_messages
# => ["Purchaser can't be blank", "Consumer email can't be blank", "Consumer email is invalid", "Consumer full name can't be blank"]
Some creative patching of the Rails errors hash will let you achieve your aim. Create an initializer in config/initalizers, let call it errors_hash_patch.rb and put the following in it:
ActiveModel::Errors.class_eval do
def [](attribute)
attribute = attribute.to_sym
dotted_attribute = attribute.to_s.gsub("_", ".").to_sym
attribute_result = get(attribute)
dotted_attribute_result = get(dotted_attribute)
if attribute_result
attribute_result
elsif dotted_attribute_result
dotted_attribute_result
else
set(attribute, [])
end
end
end
All you're doing in here is simply overriding the accessor method [] to try a little harder. More specifically, if the key you're looking for has underscores, it will try to look it up as is, but if it can't find anything it will also replace all the underscores with dots and try to look that up as well. Other than that the behaviour is the same as the regular [] method. For example, let's say you have an errors hash like the one from your example:
errors = {:"account.firstname"=>["is too short", "can not be blank"], :"account.lastname"=>["is too short", "can not be blank"], :account=>["is invalid"]}
Here are some of the ways you can access it and the results that come back:
errors[:account] => ["is invalid"]
errors[:"account.lastname"] => ["is too short", "can not be blank"]
errors[:account_lastname] => ["is too short", "can not be blank"]
errors[:blah] => []
We don't change the way the keys are stored in the errors hash, so we won't accidentally break libraries and behaviours that may rely on the format of the the hash. All we're doing is being a little smarter regarding how we access the data in the hash. Of course, if you DO want to change the data in the hash, the pattern is the same you will just need to override the []= method, and every time rails tries to store keys with dots in them, just change the dots to underscores.
As to your second question, even though I have shown you how to do what you're asking, in general it is best to try and comply with the way rails tries to do things, rather than trying to bend rails to your will. In your case, if you want to display the error messages via javascript, presumably your javascript will have access to a hash of error data, so why not tweak this data with javascript to be in the format that you need it to be. Alternatively you may clone the error data inside a controller and tweak it there (before your javascript ever has access to it). It is difficult to give advice without knowing more about your situation (how are you writing your forms, what exactly is your validation JS trying to do etc.), but those are some general guidelines.
I had the same problem with AngularJs, so I decided to overwrite the as_json method for the ActiveModel::Errors class in an initializer called active_model_errors.rb so that it can replace . for _
Here is the initializer code:
module ActiveModel
class Errors
def as_json(options=nil)
hash = {}
to_hash(options && options[:full_messages]).each{ |k,v| hash[k.to_s.sub('.', '_')] = messages[k] }
hash
end
end
end
I hope it can be helpful for someone
I'm not sure but I think you can't change that behavior without pain. But you could give a try to solutions like http://bcardarella.com/post/4211204716/client-side-validations-3-0

Resources