I have a Settings table with 5 boolean fields. I want to be able to create and update those fields via JSON. From what I understand, Rails converts all parameters to strings, so that boolean values always return true. If I send:
{ "settings": { "setting1":true, "setting2":false } }
And then try doing: Setting.new(params[:settings]), both settings will be true in the database, since the second setting's value of false is translated to "false", and thus actually evaluates to true. Actually I can't even do that, as I get:
NoMethodError (undefined method `stringify_keys' for #<String:0x000000213dcbd0>)
on that line. Some suggestions from the internet say to compare the parameters against "true", and then store that. This is a huge pain though, because then I can't take advantage of mass-assignment. I don't want to have to do this:
#setting = Setting.new
#setting.setting1 = (params[:settings][:setting1].eql? "true")
...
For all 5 fields, especially since I will have to do that for create, update, and even from other controllers (some controllers accept JSON to create a settings object along with their own attributes).
Is there a better way to go about this? JSON APIs seem pretty standard with Rails, it seems like this would be taken care of in a more elegant way?
You can pass "0" instead of false to set the value of the field to false.
How about just making a convertor class and sticking it in a before filter?
class ApplicationController < ActionController::Base
before_filter BooleanFilter
end
class BooleanFilter
def self.filter(controller)
# change all relevant string params to booleans, raise an exception if something other than "true" or "false" is detected
# eg. params[:setting][:setting1] = ( params[:setting][:setting1] == "true" ? true : false )
end
end
It is probably more consistent with Rails to assign the changed params to a new Hash object and to pass that to the model.
Related
I'm reading Rails Devise gem documentation and it says:
If the page could potentially not have a current_user set then:
if current_user.try(:admin?) # do something end
I have tried it without question mark
current_user.try(:admin)
and it works the same way returning true or false.
Do I miss something? Is there any difference and how can I see it?
Ruby is somewhat unusual in that it lets you include a wide range of characters in the names of methods including ? and !.
They have no special significance to the interpreter but the language convention is that:
methods ending with ? are interrogative - they should ALWAYS return true or false.
methods ending with ! either mutate the object the are called on or may raise a exception.
So why does it matter at all? In this particular case it does not matter since your user class has an accessor for the #admin instance variable created by ActiveRecord - just like any other column.
If it did not however current_user.try(:admin) would always return nil. Remember that instance variables are always private in Ruby until you provide an accessor*.
# Just a plain old Ruby class - not an ActiveRecord model
class User
def initialize
#admin = true
end
def admin?
#admin
end
end
User.new.try(:admin) # is always nil...
This is because User does not respond to :admin and .try prevents a NoMethodError and just returns nil instead.
ActiveRecord and accessors:
In a plain old ruby class you would add accessors to make the instance variable #admin available:
class User
def initialize
#admin = true
end
attr_accessor :admin
end
Which does this:
class User
def initialize
#admin = true
end
# getter
def admin
#admin
end
# setter
def admin=(val)
#admin = val
end
end
ActiveRecord reads the schema from your database and uses metaprograming to auto-magically add accessors to your model classes. They are a bit more complex than the example above but its the same basic principle. Thats why your User model responds to #admin.
By default, rails ActiveRecord object attributes that are boolean can either be called with or without a question mark (?).
By convention, it is easier to read if you add the ?, and that also shows that it is boolean at first glance.
So, reading this gives the impression that you are asking a question in English.
Therefore, my guess is that admin is a boolean field on the user.
Also, Tom above is very correct.
There is probably no functional difference, in this case.
I'm guessing admin is a boolean field in the users database table. So, user.admin will return either true or false -- no surprises here!
For each column in the table, Rails will also automatically generate an associated method prepended with an ?. For example, if you have a column foo, then there will be a method foo? - which will return true or false depending on the value of foo.
For example, if current_user.name == "Tom" then current_user.name? == true. And if current_user.name == nil, then current_user.name? == false.
It's very rarely necessary to use the ? methods in your code, since all objects are either "truthy" or "falsey" in ruby anyway. But it can sometimes be useful to show intent, and makes the code easier to read, as it's clear that the value is only being used in a boolean manner.
I recently 'discovered' the update_attribute method. So, I started changing sequences like
self.attribute = "foo"; save
in model or controller methods by
self.update_attribute(:attribute, "foo")
Now, the more I'm doing this, the more I'm wondering whether this is "good practice", and whether this method was intended to be used this way.
Any input from the "pro's" on this?
I would suggest using update_attribute for flags or any update operation that does not need validations since it does not fire validations. From rails documentation we can read:
Updates a single attribute and saves the record without going through
the normal validation procedure. This is especially useful for boolean
flags on existing records. The regular update_attribute method in Base
is replaced with this when the validations module is mixed in, which
it is by default.
Whereas update_attributes does:
Updates all the attributes from the passed-in Hash and saves the
record. If the object is invalid, the saving will fail and false will
be returned.
Let's look at the code now:
def update_attribute(name, value)
send(name.to_s + '=', value)
save(false)
end
def update_attributes(attributes)
self.attributes = attributes
save
end
It's always better to use update_attribute, or update_attributes if you need to update a single instance with simple data, as you can read "UPDATE" and know that you are "UPDATING".
You must know also that there is a method called update_column, that does 'kinda' the same stuff, but, update_column does NOT update the updated_at timestamp on the database.
Also, if you need to edit a large amount of instances/rows in the database with the same value, you have a method called update_all. Here is an example
#instances = Instance.all
#instances.update_all(:attribute, value)
and that will update all the attributes of that table. You will find this usefull after doing werid migrations.
Besides all of this, you can always use the 'save' way, I strongly recomend this when you have to calculate a lot of data to update a single instance. Here is an example:
#BAD
def updater_method
foo = Bar.first
foo.update_attributes(attr_one: some_calcule_method, attr_two: some_other_calcule_method, attr_three: some_more_calcule_method)
end
#GOOD
def saver_method
foo = Bar.first
foo.attr_one = some_calcule_method
foo.attr_two = some_other_calcule_method
foo.attr_three = some_more_calcule_method
etc
foo.save!
end
This will help you in debbuging, so if any method fails, you can see it clearly, with the line number and all that stuff.
Regards, Lucas.
I have a Backbone model in my app which is not a typical flat object, it's a large nested object and we store the nested parts in TEXT columns in a MySQL database.
I wanted to handle the JSON encoding/decoding in Rails API so that from outside it looks like you can POST/GET this one large nested JSON object even if parts of it are stored as stringified JSON text.
However, I ran into an issue where Rails magically converts empty arrays to nil values. For example, if I POST this:
{
name: "foo",
surname: "bar",
nested_json: {
complicated: []
}
}
My Rails controller sees this:
{
:name => "foo",
:surname => "bar",
:nested_json => {
:complicated => nil
}
}
And so my JSON data has been altered..
Has anyone run into this issue before? Why would Rails be modifying my POST data?
UPDATE
Here is where they do it:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288
And here is ~why they do it:
https://github.com/rails/rails/pull/8862
So now the question is, how to best deal with this in my nested JSON API situation?
After much searching, I discovered that you starting in Rails 4.1 you can skip the deep_munge "feature" completely using
config.action_dispatch.perform_deep_munge = false
I could not find any documentation, but you can view the introduction of this option here:
https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247
There is a possible security risk in doing so, documented here: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI
Looks like this is a known, recently introduced issue: https://github.com/rails/rails/issues/8832
If you know where the empty array will be you could always params[:...][:...] ||= [] in a before filter.
Alternatively you could modify your BackBone model's to JSON method, explicitly stringifying the nested_json value using JSON.stringify() before it gets posted and manually parsing it back out using JSON.parse in a before_filter.
Ugly, but it'll work.
You can re-parse the parameters on your own, like this:
class ApiController
before_filter :fix_json_params # Rails 4 or earlier
# before_action :fix_json_params # Rails 5
[...]
protected
def fix_json_params
if request.content_type == "application/json"
#reparsed_params = JSON.parse(request.body.string).with_indifferent_access
end
end
private
def params
#reparsed_params || super
end
end
This works by looking for requests with a JSON content-type, re-parsing the request body, and then intercepting the params method to return the re-parsed parameters if they exist.
I ran into similar issue.
Fixed it by sending empty string as part of the array.
So ideally your params should like
{
name: "foo",
surname: "bar",
nested_json: {
complicated: [""]
}
}
So instead of sending empty array I always pass ("") in my request to bypass the deep munging process.
Here's (I believe) a reasonable solution that does not involve re-parsing the raw request body. This might not work if your client is POSTing form data but in my case I'm POSTing JSON.
in application_controller.rb:
# replace nil child params with empty list so updates occur correctly
def fix_empty_child_params resource, attrs
attrs.each do |attr|
params[resource][attr] = [] if params[resource].include? attr and params[resource][attr].nil?
end
end
Then in your controller....
before_action :fix_empty_child_params, only: [:update]
def fix_empty_child_params
super :user, [:child_ids, :foobar_ids]
end
I ran into this and in my situation, if a POSTed resource contains either child_ids: [] or child_ids: nil I want that update to mean "remove all children." If the client intends not to update the child_ids list then it should not be sent in the POST body, in which case params[:resource].include? attr will be false and the request params will be unaltered.
I ran into a similar issue and found out that passing an array with an empty string would be processed correctly by Rails, as mentioned above.
If you encounter this while submitting a form, you might want to include an empty hidden field that matches the array param :
<input type="hidden" name="model[attribute_ids][]"/>
When the actual param is empty the controller will always see an array with an empty string, thus keeping the submission stateless.
I've got a form with quite a bit of params being passed to the controller for processing. The different 'sets' of params are named in a similar fashion:
setname1_paramname
setname1_paramname2
Now, I need to check one of these 'sets' to verify that all of the fields are submitted. Right now, I'm doing this with a manual If Or style statement:
if setname1_paramname.blank? || setname1_paramname2.blank? || ...etc
#object.errors.add_to_base("All setname1 fields are required.").
render :action => 'new'
return false
end
Is there way to programmatically loop over these params, and add them to the #object errors?
Thanks!
Since it sounds like you have a ton of params and also seems like you need to be able to do checks on groups of params, maybe something like this would be useful? Basically, iterate over the params hash, and use regular expressions to target sets of params. Then, inside the loop, you can do any sort of validations:
params.each do |key, value|
# target groups using regular expressions
if (key.to_s[/setname1.*/])
# whatever logic you need for params that start with 'setname1'
if param[key].blank?
#object.errors.add_to_base("All setname1 fields are required.").
end
end
end
If the names are arbitrary and of your own choosing, you could make virtual attributes for them in your model and let Rails handle the presence checking.
class SomeModel < ActiveRecord::Base
VIRTUAL_ATTRIBUTES = [:billing_address, :billing_state, :something_else]
attr_accessor *VIRTUAL_ATTRIBUTES
validates_presence_of *VIRTUAL_ATTRIBUTES
…
end
Is there a reason you wouldn't just store this information in a model, even if temporarily, and then just use rails validations for your information?
I'm rusty but I assume that even if the value is blank the param will still be returned in the params hash as long as it is coming from a form element, yes? Could you just iterate through the params hash and keep a counter of how many values are not blank and then compare the length of the params hash to the counter. If the counter is short then you have blank parameters and can handle the error that way without having to hardcode checks for each individual parameter, yes?
If what you need is a multi-step form as I suspect, you may find the Railscast on Multistep Forms to be useful
I am using Rails and mongoid to work with mongodb.
Usually in rails when working with Active:Record, you have access to the method .toggle! which simply allows you to invert the value of a boolean field in your db.
Unfortunately this method is not available for mongoDB:
user = User.first
user.toggle!(:admin)
NoMethodError: undefined method `toggle!' for #<User:0x00000100eee700>
This is unfortunate... and stupidly enough I don't see how to get around without some complicated code...
Any suggestion on how to achieve the same result concisely ?
Thanks,
Alex
ps: also one of the problems is that when I want to modify the field, it goes through validation again... and it's asking for the :password which I don't save in the db, so:
User.first.admin = !User.first.admin
won't even work :(
The issue here is specifically mongoid, not mongodb. toggle! is a part of ActiveRecord::Base, but fortunately it's not hard to replicate.
def toggle!(field)
send "#{field}=", !self.send("#{field}?")
save :validation => false
end
Add that into your model (or add it into a module, and include it in your model), and your Mongoid models will gain functionality equivalent to what you're used to in AR. It will read the field's value, invert it, write it (through the setter, per the toggle! documentation), and then save the document, bypassing validation.
# Get object's boolean field and toggle it
# #param [Object] mongoid object
# #param [String, Symbol] flag
# #example
# foo = User.find('123')
# toggle_flag!(object: foo, flag: :bar)
def toggle_flag!(object:, flag:)
object.update(flag => !object[flag])
object.save!
end
Ok the validation did not work because of a type, the code should be:
save :validate => false (not :validation)