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
Related
On Rails 5.
I have an Order model with a description attribute. I only want to validate it's presence if one of two conditions is met: if the current step is equal to the first step OR if require_validation is equal to true.
I can easily validate based on one condition like this:
validates :description, presence: true, if: :first_step?
def first_step?
current_step == steps.first
end
but I am not sure how to go about adding another condition and validating if one or the other is true.
something like:
validates :description, presence: true, if: :first_step? || :require_validation
Thanks!
You can use a lambda for the if: clause and do an or condition.
validates :description, presence: true, if: -> {current_step == steps.first || require_validation}
Can you just wrap it in one method? According to the docs
:if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. if: :allow_validation, or if: Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
validates :description, presence: true, if: :some_validation_check
def some_validation_check
first_step? || require_validation
end
You can pass a lambda to be evaluated as the if condition.
Try:
validates :description, presence: true, if: -> { first_step? || require_validation }
If you don't want to add one method as Jared say then you can try use lambda
validates :description, presence: true, if: ->{ first_step? || require_validation }
If you have a lot case , you can design for validates
validates_presence_of :price_tech_fee, if: :price_tech_fee_require?, :message => :required
validates_presence_of :percentage_tech_fee, if: :percentage_tech_fee_require?, :message => :required
def percentage_tech_fee_require?
is_active? && is_transaction_percentage? && is_premium?
end
def is_active?
!self.is_deleted && self.is_active
end
def is_transaction_percentage?
self.is_per_transaction && self.is_percentage
end
def is_premium?
....
end
I want my custom validator to only run when the properties it uses are present.
# these are all dates
validates :start_time, presence: true
validates :end_time, presence: true
validates :deadline_visitor, presence: true
validates :deadline_host, presence: true
# these validate the above dates
validate :validate_all_dates
validate :validate_dates
example:
def validate_all_dates
if self.start_time > self.end_time
errors.add(:start_time, 'must be before or the same day as end time')
end
end
fails because self.start_time and self.end_time are not present
You can add an if parameter to the validate method, this can check the conditions needed. That parameter can take either be a proc:
validates :validate_all_dates, if: Proc.new { |model| model.start_time.presence }
Or with a method:
validate :validate_all_dates, if: :dates_present?
def dates_present?
start_time.presence && end_time.presence
end
Take a look at this guide for more details: Conditional Validation
You can add the presence of the variables in the validator itself:
def validate_all_dates
if self.start_time && self.end_time && self.start_time > self.end_time
errors.add(:start_time, 'must be before or the same day as end time')
end
end
I want to validate 1 params in model method, but i can't found any fit answers , please show me the right way.
class User < ActiveRecord::Base
validate :username, presence: true, length: 4..5, unique: true
validate :email, presence: true, unique: true, format: {with: /\A[a-z0-9\.]+#([a-z]{1,10}\.){1,2}[a-z]{2,4}\z/}
def self.get_post(id)
# how to call validate id ???
validates :id, numericality: true
if id.valid?
# true code
else
# false code
end
end
def change_profile
# How to check validate user and email
username.valid?
email.valid?
# some_code....
end
end
Thanks all.
You cannot use validates there, you can do this instead
def self.get_post(id)
if id.is_a? Numeric
# true code
else
# false code
end
end
You can use active model for your customization, you can not check validation on field to filed, but you can perform with active model with number of fields as per your requirement
http://railscasts.com/episodes/219-active-model
class User
include ActiveModel::Validations
validates_with UserProfile
end
class UserProfile < ActiveModel::Validator
def validate(record)
if some_complex_logic
record.errors[:base] = "This record is invalid"
end
end
private
def some_complex_logic
# ...
end
end
is it possible to run ActiveRecord validates on given controller and action.
For example I have user_controller and signup_controller
I need to run password required validation only on signup_controller#create action
You can run validations using an if conditional:
validates :email, presence: true, if: :validate_email?
Now you need to define this instance method (in your model):
def validate_email?
validate_email == 'true' || validate_email == true
end
This validate_email attribute could be a virtual attribute in your model:
attr_accessor :validate_email
And now, you can perform email validation depending on that virtual attribute. For example, in signup_controller#create you can do something like:
def create
...
#user.validate_email = true
#user.save
...
end
use validates :password, :if => :password_changed? in user.rb
if form in users_controller does not submit password field then you should be ok.
Just a tip for implementing #markets' answer
We can use
with_options if: :validate_email? do |z|
z.validates :email, presence: true
z.validates :name, presence: true
end
for multiple validations on our specific action.
Also, we use session to pass a variable which indicate params from this action will need some validations
Controller:
before_action :no_validate, only: [:first_action, :second_action, ..]
before_action :action_based_validation, only: [:first_action, :second_action, ..]
def first_action; end
def second_action; end
def update
..
#instance.validate = session[:validate]
..
if #instance.update(instance_params)
..
end
end
private
def no_validate
session[:validate] = nil
end
def action_based_validation
# action_name in first_action will = "first_action"
session[:validate] = action_name
end
Model
attr_accessor :validate
with_options if: "validate == 'first_action'" do |z|
z.validates :email, presence: true
..more validations..
end
with_options if: "validate == 'second_action'" do |z|
z.validates :name, presence: true
..more validations..
end
more details:
http://guides.rubyonrails.org/active_record_validations.html#conditional-validation
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