How do I use validates_numericality_of with a decimal field? - ruby-on-rails

I have a table with a column (dose) that is defined as a decimal. When the user fills out the form field for this column I want to make sure they enter a valid number. I want to use validates_numericality_of :dose but this doesn't work. If the user enters a non-numeric value (e.g. 'horse') a value of 0 is saved to the database and the validation is passed. I would prefer an error was raised.
Is there a way of catching this earlier in the object lifecycle? At what point is active record converting the string to a 0?

My expectation would be that you have another plugin or hook or something that's getting in the way and changing the value before validation.
To maybe get some more insight as to what the situation is, add a before_validation hook to the model and raise self.inspect to see what the object looks like right before the validations fire. Try to save an instance with text in the :dose attribute and see what it looks like.

Related

What is the robust way to check form input values in Rails

I have a form and want the form input values to be check. If the values pass the check, then values can be stored in DB. I know I can check them in view, controller, or even model. I think probably the best way is to check them in all the three parts, and only check them in view before submit could cause problem because others can manually modify and send the request to the controller.
So for example, I have user variable in view. I don't want date type attribute user.start_date to be modify to be earlier than user.apply_date, how should I make the protection robust?
What you're looking for are model validations: http://guides.rubyonrails.org/active_record_validations.html
Check out the date_validator gem (https://github.com/codegram/date_validator)
It allows for validations like this:
validates :start_date, date: { after: :apply_date }
Doing it in the model, as that's the usual way. Ruby on Rails supports model errors extensively, and if there's an error you can simply redisplay the record and the errors in that record.
There are standard validations you can do to ensure a value is present, is unique, etc.
You can also do custom validations in the model
validate :start_date_cannot_be_before_apply_date
def start_date_cannot_be_before_apply_date
if start_date < apply_date
errors.add(:start_date, "can't be before the apply date")
end
end

validate ActiveRecord field with especific class

I want to validate that my field is from an especific class.
Concrete example. I have a model that must by unique by day. So, I insert a validation
validates :my_date_field, uniqueness: {scope: [:scope_one, :scope_two]}
If I create my record with a Date, the validation works fine. If I create my record with a Time, however, (my tests use Time::now) it parses it to a Date, but does not trigger the validation.
How should I fix it?
Thanks in advance.
--Edit--
A related problem:
How I forbid someone to do
MyModel::create my_date_field: 4
? It is passing the validate_presence from active_record, passing the null: false from migration, and saving an ugly 4 in my date_field.
I'm completely lost here =\
ActiveRecord will type-cast your values to whatever type it thinks is appropriate for the underlying database field. You can still access the original type, though, via my_date_field_before_type_cast. The *_before_type_cast accessors exist for every attribute. There you can check the class or do any other conversions you might need. These are available in custom validation methods as well.

How to use read_attribute when manually defining getter/setter (attr_accessor or attr_writers)

I set up a search model and want to require at least one field is filled out. I found a question that helps with the validation, Rails: how to require at least one field not to be blank. (I tried all answers, but Voyta's seems the best.)
The validation is working, except when I want to redefine the getter/setter via attr_accessor or attr_writer. (I have virtual attributes on the form that need to be apart of the validation.) To figure out what the problem is, I tested with an attribute that is a regular attribute item_length. If I add attr_accessor :item_length, the validation stops to work. So, I guess the question is how to I read an attribute's value without using dot notation. Since the validation uses strings, I can't use the normal way of reading.
Here is a snippet:
if %w(keywords
item_length
item_length_feet
item_length_inches).all?{|attr| read_attribute(attr).blank?}
errors.add(:base, "Please fill out at least one field")
end
Like I said, the virtual attrbutes (length_inches and length_feet) do not work at all, and the normal attribute (length) works except if I redefine the getter/setter.
You should consider read_attribute as a private method for reading Active Record columns. Otherwise you should always use readers directly.
self.read_attribute(:item_length) # does not work
self.item_length # ok
Since you are trying to call this dynamically, you can use generic ruby method public_send to call the specified method
self.public_send(:item_length) # the same as self.item_length
As stated in comment, use send
array.all? {|attr| send(attr).blank?}
For those wondering if send is ok in this case, yes it is: object calls its own instance methods.
But send is a sharp tool, so whenever you use with other objects, ensure you use their public api with public_send

Combining Virtual Attributes into one real attribute not saving from view - Active Record

I am using virtual attributes to break out a comma separated parameter list in the view, and then trying to recombine them to save into my Active Record model.
Given the real attribute "ad_columns" that defaults to "1,1,1,1"
I am breaking them out in to individual attributes for the form:
attr_accessor :top_rows
#Getter
def top_rows
split = ad_rows.split(',', 4)
split[0]
end
#Setter
def top_rows=(trows)
ad_rows_will_change!
self.ad_rows = [trows, self.right_rows, self.bottom_rows, self.left_rows].join(",")
end
Then repeating this for right, bottom, left and right.
Given an object if I call "object.top_rows" I do get "1", and if I update it in irb:
object.top_rows = "3"
Then it updates the ad_rows real attribute properly. I can see that the object has changes, and when I do an
object.save
The changes are updated in the database.
The problem is, this is NOT working from the view. It will not save to the database. I have even used logger.info to see if the model has changed and it will show that "ad_rows" has indeed been changed, yet active record is still NOT updating the real attribute.
I can't figure out why this is happening. Am I just doing it wrong? :) Thanks.
Why would it work in irb but not the view?
I would start by getting rid of attr_accessor :tops_rows.
That line creates additional and possibly conflicting instance methods for getting and setting an instance variable #top_rows and won't touch the ActiveRecord attributes.
As far as why it won't work in the view, I would check that you have added :ad_rows to attr_accessible in the model. This allows ActiveRecord to make changes to the column as part of batch operations (more than one attribute being changed). Documentation.
It seems that since the attribute :ad_rows was not being passed in the parameters, it was not being recognized as changed by the controller.
I had to add:
object.ad_rows_will_change!
To my controller to force it to save the ad_rows column.
I am not sure if this is the best solution, but it is working for now.

How does this stop mass assignment?

I wanted to start using attr_accessible with my models to stop the problem with mass assignment. I understand how it works and have researched as much as I could.
What I don't understand is the difference between using update_attributes(params[:my_form]) or create(params[:my_form]) and setting the fields one by one? Aren't both just as vulnerable?
What is the difference between NOT having attr_accessible and doing this...
#model_object = ModelObject.new
#model_object.create(params[:model_object_params])
And having attr_accessible and doing this...
#model_object = ModelObject.new
#model_object.field1 = params[:model_object_params][:field1]
#model_object.field2 = params[:model_object_params][:field2]
#model_object.field3 = params[:model_object_params][:field3]
#model_object.save!
Aren't both these methods of creating the record just as vulnerable? The hacker/cracker could send a url to both these methods and both would do just the same, right?
Or does using attr_accessible and updating the fields one-by-one do something different or somehow become safer?
There's where all these methods I'm finding of using attr_accessible don't make any sense to me. It seems to be doing the same thing two different ways. What am I missing?
Thanks.
In the way you are doing it, it does not prevent "mass assignment".
"Mass assignment" is the term used when Rails is handling the assigning of values to attributes in a model. This is typically done in a controller, using the names and values in params.
When you're doing the assigning yourself, it is also "mass assignment", in a way; but you have fine control over what to assign and what not to in this case. So, to save writing that boilerplate assignment code, Rails provides attr_accesible - same control, less code.
To see how it is used:
Presume that a ActivityLog model has an attribute called user_ip_address.
Now, user_ip_address is an attribute in the model, and could be assigned by mass-assignment or by "self-rolled-mass-assignment".
But in both cases that is wrong -- you don't want user-supplied input to set a value for that attribute.
Instead, you want to always find out the actual IP address of the user and assign that value (ignoring any
value in params). So you would exclude user_ip_address from attr_accessible and instead assign it yourself.
attr_accessible :all_attributes_except_user_ip_address
#al = ActivityLog.new(params[:model_object_params])
#al.user_ip_address = get_origin_user_ip_address
#al.save
For any information that a user should not be able to change, use attr_accessible and exclude it from the list.
The short answer is that it stops field4 from being set implicitly.
The difference is that without attr_accessible a hacker could update a field that is not in your form. With attr_accessible this impossible.
E.g. if your user model has a field is_admin, a hacker could try to create a new admin by posting:
params[:user][:is_admin] = true
If attr_accessible is set (and obviously it shouldn't contain is_admin) this is impossible.
About your example: if your model only has field1, field2 and field3 and there are no other database columns you want to protect, there is no need to use attr_accessible. Hope this makes it clear.
Just remember:
Without any precautions
Model.new(params[:model]) allows
attackers to set any database column’s
value.
Source: http://guides.rubyonrails.org/security.html#mass-assignment
The idea here is to limit the parameters you will accept for a given model. Then you can test each of them either with a validation or some other code to be sure they fit expected values.
Attr_accessible is intended to limit the "surface" of your model to what you intend to accept and check carefully. In this way you can automatically ignore an injected parameter like :roles => "admin" in case you add that feature to your model
user.update_attributes(params[:user])
Since the roles attribute is not listed in attr_accessible, the user's attempt to become an administrator is fruitless.
You want to handle the validation logic in one place (your model), instead of checking each parameter value in your controller.
Mass assignment isn't something you prevent, it's something you control. It's a good feature, one that makes things easier and cleaner, but without some kind of ability to control what gets set via mass assignment it's a potential security hole. attr_accessible, as others have mentioned, provides that control.

Resources