I have four date_time fields in my model in Rails app. I want to apply the same validation method to them, so that only a valid date time could be accepted. Validation method is from earlier question on stack overflow:
validate :datetime_field_is_valid_datetime
def datetime_field_is_valid_datetime
errors.add(:datetime_field, 'must be a valid datetime') if ((DateTime.parse(datetime_field) rescue ArgumentError) == ArgumentError) && !datetime_field.nil? && !datetime_field.blank?
end
Is there more elegant way to validate these fields, other than defining four exactly same methods for every DateTime field?
Best solution is to create your own validator:
class MyModel < ActiveRecord::Base
include ActiveModel::Validations
class DateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << "must be a valid datetime" unless (DateTime.parse(value) rescue nil)
end
end
validates :datetime_field, :presence => true, :date => true
validates :another_datetime_field, :presence => true, :date => true
validates :third_datetime_field, :presence => true, :date => true
end
UPD
you can share same validations this way:
validates :datetime_field, :another_datetime_field, :third_datetime_field, :presence => true, :date => true
def self.validate_is_valid_datetime(field)
validate do |model|
if model.send("#{field}?") && ((DateTime.parse(model.send(field)) rescue ArgumentError) == ArgumentError)
model.errors.add(field, 'must be a valid datetime')
end
end
end
validate_is_valid_datetime :datetime_field
Related
I've got a service object which has several validations that validate two params. Everything works fine until these params are "". In this case, even though I validate their presence, later validations raise errors. How can I make my code validate the presence first and then, only if the values are present, continue the validations?
class SubscriptionPause
include ActiveModel::Model
extend ActiveModel::Naming
attr_accessor :paused_from, :paused_till, :params, :id
validates :paused_from, presence: true, allow_nil: true
validates :paused_till, presence: true, allow_nil: true
validate :paused_from_cant_be_later_than_subscription_ends
validate :paused_from_cant_be_in_the_past
validate :paused_till_is_later_than_paused_from
def initialize(params)
#params = params
#paused_form = params[:paused_from]
#paused_till = params[:paused_till]
end
def create
if valid?
...
else
...
end
end
private
def subscription
#subscription || Subscription.find(params[:id])
end
def paused_from_cant_be_in_the_past
if !paused_from.empty? && paused_from.to_date < Date.today
errors.add(:paused_from, I18n.t("..."))
end
end
def paused_till_is_later_than_paused_from
if paused_from > paused_till
errors.add :paused_from, I18n.t("...")
end
end
def paused_from_cant_be_later_than_subscription_ends
if !paused_from.empty? && subscription.expire_date < paused_from
errors.add :paused_from, I18n.t("...")
end
end
end
Based on your comment above, it sounds like you never want the from or till to be nil so remove allow_nil: true. Then just add a conditional to the other validations as suggested by Rahul
validates :paused_from, presence: true
validates :paused_till, presence: true
validate :paused_from_cant_be_later_than_subscription_ends, if: :params_present?
validate :paused_from_cant_be_in_the_past, if: :params_present?
validate :paused_till_is_later_than_paused_from, if: :params_present?
def params_present?
paused_from.present? && paused_till.present?
end
P.S. don't use and over && unless you know why (suggested by Rahul). && is better in nearly all cases. Difference between "and" and && in Ruby?
You could do something like this:
validate :paused_from_cant_be_later_than_subscription_ends, :if => :params_present?
validate :paused_from_cant_be_in_the_past, :if => :params_present?
validate :paused_till_is_later_than_paused_from, :if => :params_present?
def params_present?
return params[paused_from].present? and params[paused_till].present?
end
I have field date in the User model. It shuld have only future dates (from registration moment). What is the best way to validation it in the model?
I think about something like this:
class User < ActiveRecord::Base
validates :name, presence: true
validates :date, presence: true
validate :future_event
private
def future_event
errors.add(:date, "Can't be in the past!") if date < Time.now
end
end
Is it OK?
Can you think about some more simple and elegant solution?
# lib/future_validator.rb
class FutureValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if record[attribute] < Time.now
record.errors[attribute] << (options[:message] || "can't be in the past!")
end
end
end
class User < ActiveRecord::Base
validates :name, presence: true
validates :date, presence: true
validates :future_event, future: true
end
http://guides.rubyonrails.org/active_record_validations.html#custom-validators
Take a look at the Date Validator Gem
validates :start_date,
date: { after: Proc.new { Date.current }, message: 'must be after today' },
on: :create
You might always want to investigate the differences between Date.current and Date.today - Date.current is timezone aware and will use the Timezone of your Rails app. Date.today uses system time. There can be odd differences if one is UTC and one is Eastern.
Use inclusion.
Something like this:
validates :date, inclusion: { in: Proc.new{ 1.day.from_now.. }, message: "has to be in the future" }
I'm trying to create a validation statement that validates an object if service_area is present UNLESS service_area_radius==0
Here's the statement I created, which doesn't work:
validates :service_area, :presence => true, unless: "service_area_radius==0"
http://railscasts.com/episodes/41-conditional-validations
Like this:
validates_presence_of :password, :if => :should_validate_password?
validates_presence_of :country
validates_presence_of :state, :if => :in_us?
attr_accessor :updating_password
def in_us?
country == 'US'
end
def should_validate_password?
updating_password || new_record?
end
validates :service_area,
presence: {message: "Area Radius is missing."}, if: :radius_found?
private
def radius_found?
service_area_radius > 0
end
The validation for service_area will be executed if radius_found? returns true.
radius_found? will return true when the service_area_radius(attribute) hold value > 0.
Adding a custom message with message: option, when the validation fails.
This is my model class:
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date :presence => true
# custom validators
validate :dates_cant_be_in_the_past
def dates_cant_be_in_the_past
if Date.parse(beginning_date) < Date.today
errors.add(:beginning_date, "cant be in the past")
end
if Date.parse(end_date) < Date.today
errors.add(:end_date, "cant be in the past")
end
end
end
Now two things should happen: At first validate the presence of the beginning_date and end_date attributes and than run my dates_cant_be_in_the_pastvalidator.
Sadly this approach doesn't work. If I leave a field empty the Date.parsemethod throws an exception, because the argument is obviously empty.
Is it possible to define the order of default and custom validations? Or do I have to implement the presence validator myself, so I would do something like:
validate :dates_cant_be_blank, :dates_cant_be_in_the_past
The guide at least says:
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
Thank in advance
It's much simpler if you create a validator for that:
class DateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if Date.parse(value) < Date.today
record.errors.add(attribute, "cant be in the past")
end
end
end
And at your model you would use it like this:
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date :presence => true
validates :beginning_date, :end_date, :date => true, :allow_blank => true
end
The :allow_blank piece if the one prevents the validation from running if the value is empty. Using a real validator object also removes the code form your model making it much simpler and removing the duplication you currently have.
You could try something like this
class Availability < ActiveRecord::Base
attr_accessible :beginning_date, :end_date
validates :beginning_date, :end_date : presence => true
# custom validators
validate :valid_dates
def valid_dates
if valid_string(beginning_date)
errors.add(:beginning_date, "Can't be in the past") unless Date.parse(beginning_date) > Date.today
end
if valid_string(end_date)
errors.add(:end_date, "Can't be in the past") unless Date.parse(end_date) > Date.today
end
end
def valid_string(test_value)
test.value.is_a? String
end
end
Is there a way to validate an input field using just a few lines of code?
Right not I've to do this when using a custom validation.
Model
class Search < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
Validator
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
end
end
end
This is what I really want to do.
class Search < ActiveRecord::Base
validates :email, :email => lambda do |record, value|
record.errors[:email] << "invalid field" unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
end
end
Is that possible?
Take a look at ActiveModel's validates_format_of http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_format_of
class Person < ActiveRecord::Base
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
end
Or use the shortcut:
class Person < ActiveRecord::Base
validates :email, :format => { :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }
end
Here is my solution.
class Search < ActiveRecord::Base
validate :my_custom_validator
def my_custom_validator
if #email.to_s.match(/hello/)
self.errors[:email] << "Email can't contain the word 'hello'"
end
end
end