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
Related
I'm having trouble with a little Ruby on Rails I'm building and need some help.
I have a Table with 20+ Columns and a corresponding XML File which can be parsed as some sort of hash with a gem. Every key would be mapped to a column and every value would be a data record in said column.
The way I access a specific value in the already parsed XML file is:
filename["crs","inputkeyhere"]
which returns the value, for example "52" or whatever.
What I am trying to do is upload the file, parse it with the gem and give each column the corresponding value.
My table (or model) is called "Attributeset" and I already know how I can access every column:
#attributeset = Attributeset.new
#attributeset.attributes.keys
So my thought process was:
Iterate over all the keys
Pass every key into a block called |a|
Use the rails possibilty to set attributes by calling the corresponding #attributeset.
Set colum attribute to the corresponding xml key
So my code would go something like this:
#attributeset.attributes.keys.each do |a|
#attributeset.a=filename["crs",a]
end
But my problem is, that ruby thinks ".a" is a method and apparently does not evaluate "a" to the block parameter.
I've read through lambdas and procs and whatnot but didn't really understand how they could work for my specific situation.
Coming from bash scripting maybe my thinking might be wrong but I thought that the .a might get evaluated.
I know I can run the block with yield, but this only works in methods as far as I know..
Any help is appreciated.
Thanks and stay healthy,
Alex
Thanks for the input!
I wanted to make it as clean as possible, and not using any temporary hashes to pass arguments.
I've found the method
write_attribute
which can be used like this:
#attributeset.write_attribute(a, xmp["crs",a])
worked perfectly for me.
You can use []= method to set values dynamically:
#attributeset.attribute_names.each do |attribute|
#attributeset[attribute] = filename["crs", attribute]
end
I am upgrading an app to Rails 4.2 and am running into an issue where nil values in a field that is serialized as an Array are getting interpreted as an empty array. Is there a way to get Rails 4.2 to differentiate between nil and an empty array for a serialized-as-Array attribute?
Top level problem demonstration:
#[old_app]
> Rails.version
=> "3.0.3"
> a = AsrProperty.new; a.save; a.keeps
=> nil
#[new_app]
> Rails.version
=> "4.2.3"
> a = AsrProperty.new; a.save; a.keeps
=> []
But it is important for my code to distinguish between nil and [], so this is a problem.
The model:
class AsrProperty < ActiveRecord::Base
serialize :keeps, Array
#[...]
end
I think the issue lies with Rails deciding to take a shortcut for attributes that are serialized as a specific type (e.g. Array) by storing the empty instance of that type as nil in the database. This can be seen by looking at the SQL statement executed in each app:
[old_app]: INSERT INTO asr_properties (lock_version, keeps)
VALUES (0, NULL)
Note that the above log line has been edited for clarity; there are other serialized attributes that were being written due to old Rails' behavior.
[new_app]: INSERT INTO asr_properties (lock_version)
VALUES (0)
There is a workaround: by removing the "Array" declaration on the serialization, Rails is forced to save [] and {} differently:
class AsrProperty < ActiveRecord::Base
serialize :keeps #NOT ARRAY
#[...]
end
Changing the statement generated on saving [] to be:
INSERT INTO asr_properties (keeps, lock_version) VALUES ('---[]\n', 0)
Allowing:
> a = AsrProperty.new; a.save; a.keeps
=> nil
I'll use this workaround for now, but:
(1) I feel like declaring a type might allow more efficiency, and also prevents bugs by explicitly prohibiting the wrong data type being stored
(2) I'd really like to figure out the "right" way to do it, if Rails does allow it.
So: can Rails 4.2 be told to store [] as its own thing in a serialized-as-Array attribute?
What's going on?
What you're experiencing is due to how Rails 4 treats the 2nd argument to the serialize call. It changes its behavior based on the three different values the argument can have (more on this in the solution). The last branch here is the one we're interested in as when you pass the Array class, it gets passed to the ActiveRecord::Coders::YAMLColumn instance that is created. The load method receives the YAML from the database and attempts to turn it back into a Ruby object here. If the coder was not given the default class of Object and the yaml argument is nil in the case of a null column, it will return a new instance of the class, hence the empty array.
Solution
There doesn't appear to be a simple Rails-y way to say, "hey, if this is null in the database, give me nil." However looking at the second branch here we see that we can pass any object that implements the load and dump methods or what I call the basic coder protocol.
Example code
One of the members of my team built this simple class to handle just this case.
class NullableSerializer < ActiveRecord::Coders::YAMLColumn
def load(yaml)
return nil if yaml.nil?
super
end
end
This class inherits from the same YAMLColumn class provided by ActiveRecord so it already handles the load and dump methods. We do not need any modifications to dump but we want to slightly handle loading differently. We simply tell it to return nil when the database column is empty and otherwise call super to work as if we made no other modification.
Usage
To use it, it simply needs to be instantiated with your intended serialization class and passed to the Rails serialize method as in the following, using your naming from above:
class AsrProperty < ActiveRecord::Base
serialize :keeps, NullableSerializer.new(Array)
# …
end
The "right" way
Getting things done and getting your code shipped is paramount and I hope this helps you. After all, if the code isn't being used and doing good, who cares how ideal it is?
I would argue that Rails' approach is the right way in this case especially when you take Ruby's philosophy of The Principle of Least Surprise into account. When an attribute can possibly be an array, it should always return that type, even if empty, to avoid having to constantly special case nil. I would argue the same for any database column that you can put a reasonable default on (i.e. t.integer :anything_besides_a_foreign_key, default: 0). I've always been grateful to past-Aaron for remembering this most of the time whenever I get an unexpected NoMethodError: undefined method 'whatever' for nil:NilClass. Almost always my special case for this nil is to supply a sensible default.
This varies greatly on you, your team, your app, and your application and it's needs so it's never hard and fast. It's just something I've found helps me out immensely when I'm working on something and wondering if amount could default to 0 or if there's some reason buried in the code or in the minds of your teammates why it needs to be able to be nil.
This is using Rails 4.2.0, Ruby 2.2.0.
What I'd like to do is use the data contained in a fixture object to verify that duplicates are caught before insertion into the same database:
test "identical entries should be impossible to create" do
dup_entry = Entry.new(entries(:test_entry))
assert_not dup_entry.save
end
(where Entry is a well-defined model with a controller method .new, and test_entry is a fixture containing some valid Entry instance.)
Unfortunately, this doesn't work because entries(:test_entry) is going to be an Entry, not a hash accepted by Entry.new.
I know that I can access fixture properties with an expression of the form fixture_objname.property in the associated tests, since whatever is specified in the YAML will automatically be inserted into the database and loaded. The problem with this is that I have to manually retype a bunch of property names for the object I just specified in the YAML, which seems silly.
The documentation also says I can get the actual model instances by adding self.use_instantiated_fixtures = true to the test class. However, there don't seem to be any instance_methods that will dump out the fixture's model instance (test_entry) in a hash format to feed back into the .new method.
Is there an idiomatic way to get what I want, or a different, easier way?
I believe you're looking for something like:
entries(:test_entry).attributes
entries(:test_entry).attributes.class # => Hash
You can also remove properties if needed:
entries(:admin).attributes.except("id")
Hope this helps.
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
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