Only future dates - validation in Rails model - ruby-on-rails

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" }

Related

Rails Model Custom Validation Won't Work On Update

I try to create some model validation on my Coupon Model
evrything work on create, but when i try to update data the validation won't validate
the first validation is to raise error if percentage more than 100
the second validation is to validate if date start bigger than date expired it will throw some error
my model in this code:
# frozen_string_literal: true
class Coupon < ApplicationRecord
has_many :transactions, dependent: :restrict_with_exception
has_one :battery, through: :transactions
enum :discount_type, %i[percentage nominal]
validates :code, format: { with: /\A[0-9A-Z]+\Z/ },
presence: true,
uniqueness: true,
length: { maximum: 50 },
on: :create,
allow_nil: false
validate :discount_value, :discount_type
validate :discount_value_percentage
validates :start_at, :expired_at, presence: true
validate :end_date_after_start_date
def discount_value_percentage
return unless discount_type == 'percentage' && discount_value > 100
errors.add(:discount_value, 'discount value percentage cannot over 100%')
end
def end_date_after_start_date
return if expired_at.blank? || start_at.blank?
return unless expired_at < start_at
errors.add(:expired_at, 'must be after the start date')
end
end
Please help me to resolve my problem
the validation can validate if date start bigger than date end it will throw error code

How to access the parameter being validated

In the following example, is there a way to retrieve the name of the parameter being currently validated inside the if proc ?
class MyModel < ActiveRecord::Base
with_options if: proc{|o| "how to retreive the parameter being validated here?"} do
validates :param_1, presence: true
validates :param_2, presence: true
end
end
I would like to avoid this kind of solution:
class MyModel < ActiveRecord::Base
validates :param_1, presence: true, if: proc{|o| o.need_validation?(:param_1)}
validates :param_2, presence: true, if: proc{|o| o.need_validation?(:param_2)}
end
If you wish to know name, and other data like option to validation, use validators:
app/validators/param_validator.rb:
ParamValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# your validation code here...
end
end
The arguments say about themself.
Use it in a model:
validates :param_1, param: true
validates :param_2, param: true
If each of the validations are identical apart from the name, you could iterate over them:
class MyModel < ActiveRecord::Base
[:param1,:param2].each do |param|
validates param, presence: true, if: proc{|o| o.need_validation?(param) }
end
end
I'm pretty sure there is no easy answer to my question, I'll find an other way.

How to use default and custom validators together? How to define their order?

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

Rails Model Validation Conditional allow_nil?

So I was wondering if we can have a conditional allow_nil option for a validation on a rails model.
What I would like to do is to be able to allow_nil depending upon some logic(some other attribute)
So I have a product model which can be saved as draft. and when being saved as draft the price can be nil, but when not a draft save the price should be numerical. how can I achieve this. The following doesn't seem to work. It works for draft case but allows nil even when status isn't draft.
class Product<ActiveRecord::Base
attr_accessible :status, price
validates_numericality_of :price, allow_nil: :draft?
def draft?
self.status == "draft"
end
end
Looking at rails docs I looks like there isn't an option to pass a method to allow_nil?
One possible solution is to do separate validations for both cases
with_options :unless => :draft? do |normal|
normal.validates_numericality_of :price
end
with_options :if => :draft? do |draft|
draft.validates_numericality_of :price, allow_nil: true
end
Any other ways to get this working ?
Thanks
You can use if and unless to do the following
class Product<ActiveRecord::Base
attr_accessible :status, price
validates_numericality_of :price, allow_nil: true, if: :draft?
validates_numericality_of :price, allow_nil: false, unless: :draft?
def draft?
self.status == "draft"
end
end
With the above, you will have 2 validations set up, one that applies when draft? == true, which will allow nils, and one where draft? == false which will not allow nils
You don't need allow_nil then, just use if:
class Product < ActiveRecord::Base
attr_accessible :status, price
validates_numericality_of :price, if: -> { (draft? && !price.nil?) || !draft? }
def draft?
self.status == "draft"
end
end

Same custom validation for several fields in Rails

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

Resources