I have the following validation of product's price:
class Product < ActiveRecord::Base
...
PRICE_REGEX = /^([1-9]\d{0,5}|0)(\.\d{1,2})?$/
validates :price, :presence => true, :format => PRICE_REGEX
...
end
It supposed to allow prices from 0 to 999999.99.
However, if I enter hello, the validation passes, and 0.00 is saved in the database.
The :presence validation works fine, though.
What am I missing here ?
The price column is a float, and so Rails will automatically convert the string "hello" to float, as per "hello".to_f # => 0.0. This is then converted back to the string "0.0", which obviously matches the regular expression.
In general, it's a bad idea to use regular expressions on non-string columns. Instead, use validates_numericality_of. If you wanted the same restriction as with the regex, do it like this:
class Product < ActiveRecord::Base
validates_numericality_of :price, :greater_than => 0, :less_than => 1000000
end
Not only is it safer, but it's easier to read and follow as well. Note that it'll automatically reject the price if blank as well.
I haven't tested this, but every description I saw from validates uses :format in this way:
validates :price, :presence => true, :format => { :with => PRICE_REGEX }
Maybe the problem is there
Related
I would like to validate my code's length.
It suppose to take a number from a range in between 13 and 16 but it should exclude 14.
I cannot find any clever way to do it in Model.
class Card < ActiveRecord::Base
validates :number, :length => { in: 13..16 }
end
Here it is:
validates :number, :length => { in: [13,15,16] }
However, it smells like you probably should be using a regular expression.
My Requirement :
To Add validation to make sure that SITEMAP_URL keyword is always included in search_engines.url and it should be of format http://www.example.com/
class SearchEngine < ActiveRecord::Base
validates :name, :ping_url, :uniqueness => true, :presence => true
validates :ping_url, :format => {:with => /\=SITEMAP_URL$/}, :presence => true
end
Is the validation i have written is correct .
The validation looks fine.
Except if you expect /\=SITEMAP_URL$/ to interpolate it won't.
It will only match strings like;
"http://some.com?q=SITEMAP_URL"
Is that really what you mean?
If you have
SITEMAP_URL = "/sitemap.xml"
Then you can say
SITEMAP_REGEXP = /\=#{SITEMAP_URL}/
But this will interpret the "." as any character.
So you need to use Regexp.escape
Pretty much
Regexp.new(Regexp.escape("=#{SITEMAP_URL}")+"$")
Should do what you want.
(A) Like this:
validates :network_id, :numericality => true
validates :direct_url, :presence => true
validates :domain_name, :presence => true
validates :merchant_id, :numericality => true
validates :is_paid_merchant, :presence => true
validates :is_top_merchant, :presence => true
validates :last_months_revenue, :presence => true,
:numericality => true
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :primary_category_id, :numericality => true
validates :url, :presence => true
validates :url_tag, :presence => true,
:length => { :maximum => 45 }
-OR-
(B) like this:
validates :network_id,
:merchant_id,
:last_months_revenue,
:primary_category_id, :numericality => true
validates :direct_url,
:domain_name,
:is_paid_merchant,
:is_top_merchant,
:last_months_revenue,
:name,
:url,
:url_tag, :presence => true
validates :name, :length => { :maximum => 50 }
validates :url_tag, :length => { :maximum => 45 }
In the first case each field has it's own validates clause and in the second it's based on what is being validated (fields that have multiple validations appear multiple times). The first case is also in alphabetical order so is a little more helpful to jump right to a specific field.
-OR-
(C) Am I just too anal retentive about how my code reads and looks?
I think the first style is better for the following reason:
It's most likely that you have a specific field name 'in mind' when working on a validation - or editing one - or adding a new validation for that field. So a system that lists each field and then all the validations for each one works well to read.
The other style - group validations together tends to lead to seeing a field in one spot but then having to search and scroll around for other validations for that field - and there's a good chance that you will not see or know about the other validations for that field which may be 'off-screen' and thus missed.
This also may not be that big a deal when initially building the application (when the first style might actually seem easier) but going forward when you are adding or removing or changing fields and validations (i.e. 'application building in the real world!') it will be easier and less error-prone if all the validations for a given field are together.
Another example of why B) is bad...
Imagine this:
validates :network_id,
:merchant_id,
:last_months_revenue,
:primary_category_id, :numericality => true
validates :network_id,
:direct_url,
:domain_name,
:merchant_id,
:url,
:url_tag, :presence => true
validates :network_id :uniqueness => true
See how field are repeated all over the place?
Now imagine removing network_id - yuch!
Now imagine adding another _id field that needs numericality, uniqueness and presence - yuch!
Another example - one might think (ok, I've done long ago), i'll group all the 'required's together and then put a nice comment for them and then all the uniquesness of's, with a comment header label for all of them, etc. So there is a 'standard' for developers to follow. The problem with an approach like this (apart from the previous comments) is that this is a 'local' standard that other programmers (both current and future) will need to understand and then... hopefully... follow. Much as I love them myself, personal standards like this often contribute to technical debt unless clearly thought out.
Personally I'd prefer the first style. Because I'd kick you for having to read through 30 lines of code to find out what validations are set on a single field.
For me better is A. One declaration for one attribute is not redundant but clear.
In a Ruby on Rails tutorial, I am asked to type:
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
end
I understand what this does, but I would like to know what the => operator is. In PHP-land, it links a key and a value in an associative array. Is it the same thing here? Is it a Ruby operator or a Rails operator?
It is mainly a ruby operator that sets the value of a key inside a hash. Thus :
{ :minimum => 5 }
Is a ruby hash that has the symbol :minimum as a key that maps to the value of 5. A hash with one entry, in this example. Same for :
:presence => true
Still a hash. However, in ruby, when you have a method, you can omit the {} that surround a hash. That is what happens with the validates method. It's a method and thus the passed hash does not explicitly need {}.
Airports have four-letter ICAO codes. By convention, these are always uppercase. I'm creating a form for receiving user input, but this form needs to be able to accept user input in mixed case, and prevent them from creating dupes.
The default :uniqueness is case-sensitive, of course. I figured out how to transform the user's input to uppercase before it gets saved, but the problem is that this appears to be post-validation, instead of pre-validation.
For example, if there is already an Airport with ICAO of KLAX, a user can enter klax, it will get validated as unique, and then transformed to uppercase and stored, resulting in duplicates.
Here's my model code at present.
class Airport < ActiveRecord::Base
validates :icao, :name, :lat, :lon, :presence => true
validates :icao, :uniqueness => true
before_save :uppercase_icao
def uppercase_icao
icao.upcase!
end
end
Or a slightly different take: Write a setter for icao that converts anything thrown at it to uppercase:
def icao=(val)
self[:icao] = val.upcase
end
And then you can use regular uniqueness validation (back it up with a unique index in your DB). Might even make things a little easier for the DB during finds, 'cause it doesn't have to worry about case-insensitive comparisons any more.
Hope this helps!
try this:
validates :icao, :uniqueness => { :case_sensitive => false }
Updated answer for Rails 4.
class Airport < ActiveRecord::Base
validates :icao, :name, :lat, :lon, :presence => true
validates :icao, :uniqueness => { case_sensitive: false }
def icao=(val)
write_attribute :icao, val.upcase
end
end
Simply fixed (as many problems with Rails are) - as Danny pointed out above, although not in his own answer so I can't accept it :), changing before_save to before_validation fixes it perfectly.