I have money-rails gem installed and it's working flawlessly. However, there are no validations on money objects when I create a new model record. If I try to input letters into the money field, the form submission is successful, it just sets the value to 0. How do I get those validations working? I have no code for the actual validation, seeing as money-rails on github states that it has validations included, but I have tried validates_numericality_of to no avail.
EDIT
Yes I have read the docs extensively, and tried the validation option suggested on Github.
I have this example hope it could help you
monetize :price_cents, :numericality => {:greater_than => 0, :less_than => 10}
validates :price, :format => { :with => /\A\d+(?:\.\d{0,2})?\z/ }
just like this one of them (monetize validation) will validate the numericality stuff and the other one will validate the format.
I need to validate few attributes in my model only when they are present in params while updating or creating the object.
validates :attribute_a,format: {with: /some_regex/}, :if => lambda{ |object| object.attribute_a.present? }
Like attribute_a there are multiple attributes which may not be present while being created or updated.Instead of writing the validates statement for each of them,is there a way in which I can check the presence of multiple attributes and then validate every one with a common validation such as inclusion_inor format:{with:/some_regex/ }.
I wanted something like the following code which obviously is wrong.
validates :attribute_a,attribute_b,attribute_c,attribute_d,:format => {:with =>
/some_regex/}, :if => lambda{ |object| object.attribute_name.present? }
You can use validates_format_of:
validates_format_of :attr_a, :attr_b,
with: /someregexp/,
allow_blank: true
The allow blank option means that the regexp doesn't have to match if the attribute is not present.
I use time_select with a prompt ("Choose hours"...) and would like to make sure, the user actually enters a valid time. But if the form is submitted without a time being selected (so with the empty prompt options selected) I don't get any errors from my validations.
My Validation:
validates :start_time, :presence => true, :allow_nil => false, :allow_blank => false
The reason is that rails turns the input from the time_select fields automatically into this date, when they are empty:
0001-01-01 00:00:00
Is there a nice way to prevent this from happening?
Thanks!
If you want to enforce a validation it looks like, at least from this SO Post and this SO Post, you would want to do a custom validation.
class Whatever < ActiveRecord::Base
validate :time_scope
...
private
def time_scope
if Whatever.where(<pick the record entered>).time == "0001-01-01 00:00:00"
errors.add(:time, "Oh no you didn't enter that garbage time on my watch!")
end
end
end
Obviously this is a silly example but I hope it gets the idea across.
I have a model with 2 validations on the 'name' attribute. It goes something like this:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") if name_is_dumb?
end
I don't want the 2nd validation to run if the first validation fails (the name is not unique).
What's the best and cleanest way to do this?
According to the documentation:
Callbacks are generally run in the
order they are defined, with the
exception of callbacks defined as
methods on the model, which are called
last.
So the following snippet should work:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") unless errors[:name].nil?
end
I have several models with a date attribute and for each model I'd like to validate these dates against a given range. A basic example would be:
validates_inclusion_of :dated_on, :in => Date.new(2000,1,1)..Date(2020,1,1)
Ideally I'd like to evaluate the date range at runtime, using a similar approach as named_scope uses, e.g:
validates_inclusion_of :dated_on, :in => lambda {{ (Date.today - 2.years)..(Date.today + 2.years)}}
The above doesn't work of course, so what is the best way of achieving the same result?
You can use the date validator:
validates :dated_on, :date => {:after => Proc.new { Time.now + 2.years },
:before => Proc.new { Time.now - 2.years } }
If the validation is the same for each class, the answer is fairly simple: put a validation method in a module and mix it in to each model, then use validate to add the validation:
# in lib/validates_dated_on_around_now
module ValidatesDatedOnAroundNow
protected
def validate_dated_around_now
# make sure dated_on isn't more than five years in the past or future
self.errors.add(:dated_on, "is not valid") unless ((5.years.ago)..(5.years.from_now)).include?(self.dated_on)
end
end
class FirstModel
include ValidatesDatedOnAroundNow
validate :validate_dated_around_now
end
class SecondModel
include ValidatesDatedOnAroundNow
validate :validate_dated_around_now
end
If you want different ranges for each model, you probably want something more like this:
module ValidatesDateOnWithin
def validates_dated_on_within(&range_lambda)
validates_each :dated_on do |record, attr, value|
range = range_lambda.call
record.errors.add(attr_name, :inclusion, :value => value) unless range.include?(value)
end
end
end
class FirstModel
extend ValidatesDatedOnWithin
validates_dated_on_within { ((5.years.ago)..(5.years.from_now)) }
end
class SecondModel
extend ValidatesDatedOnWithin
validates_dated_on_within { ((2.years.ago)..(2.years.from_now)) }
end
validates :future_date, inclusion: { in: ->(g){ (Date.tomorrow..Float::INFINITY) }
A different solution is to rely on the fact that validates_inclusion_of only requires an :in object that responds to include?. Build a delayed-evaluated range as follows:
class DelayedEvalRange
def initialize(&range_block)
#range_block = range_block
end
def include?(x)
#range_block.call.include?(x)
end
end
class FirstModel
validates_inclusion_of :dated_on, :in => (DelayedEvalRange.new() { ((5.years.ago)..(5.years.from_now)) })
end
The simplest and working solution is to use the in-built validation from Rails. Just validates it like that:
validates :dated_on,
inclusion: { in: (Date.new(2000,1,1)..Date(2020,1,1)) }
if you need to validate the presence as well just add another validation:
validates :occurred_at,
presence: true,
inclusion: { in: (Date.new(2000,1,1)..Date(2020,1,1)) }
Remember that you can always use helpers like 1.day.ago or 1.year.from_now to define ranges.
A special care should be taken when using relative dates. Consider the following example where the age should be in range 18..65:
validates :birthdate,
inclusion: { in: 65.years.ago..18.years.ago }
A person which is valid? # => true today, will suddenly become invalid once they turn 66.
This might lead to many unexpected cases, where your model cannot be updated anymore because this validation fails.
A clever way to handle this case is to validate the field only when it actually changes:
validates :birthdate,
inclusion: { in: 65.years.ago..18.years.ago },
if: ->(model) { model.birthdate.present? && model.birthdate_changed? }
Ask yourself if you need to evaluate the range at runtime. A good developer doesn't always go for correctness, if the price is complexity, imho.
I wanted to sanity check that a date was reasonable, i.e. within a few years from now. It doesn't really matter if that turns out to be a range of several years around today or two weeks ago, and my server processes aren't likely to still be running in a few years. In the end, I decided that the simpler solution (just a straight inclusion :in => reasonable_date_range) was preferable to a correct, but more complicated solution.
That said, I like James A. Rosen's solution with DelayedEvalRange. If your range is just a couple of days, or correctness is important for some other reason, I'd go with that.
The simplest solution for me was:
validate :validate_dob
def validate_dob
unless age_range.cover?(dob.present? && dob.to_date)
errors.add(:dob, "must be between %s and %s" % age_range.minmax)
end
end
However, if you want to remove duplications, I suggest moving it in your own validator.
And yes, I want runtime execution because my code will run in a couple of years, and after more than a year I didn't deploy, I surely don't want to bring up the project again.