Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A more elegant way to skip validations in Rails?

In my User model, I have the usual suspects of email, first_name, last_name, password, etc.

I have several cases where I need to skip all or some of the validations.

Currently, I have an unless condition that looks something like:

  validates :first_name, presence: :true, etc..., unless: :skip_first_name_validation?
  validates :last_name, presence: :true, etc..., unless: :skip_last_name_validation?
  validates :email, presence: :true, etc..., unless: :skip_email_validation?

Of course I also have:

  attr_accessor :skip_first_name_validation, :skip_last_name_validation, etc.

And then I use private methods to check the state of each:

  def skip_first_name_validation?
    skip_first_name_validation
  end

  def skip_last_name_validation?
    skip_last_name_validation
  end

  def skip_email_validation?
    skip_email_validation
  end

  etc..

From there, whenever I need to skip validations, I just assign each one of these guys a true value in my controller.


So while all of this works fine, I'm wondering if there's a more elegant way?

Ideally it would be nice if I could use a simple conditional like this for each attribute in my models:

:skip_validation?

And in my controllers, just do something like:

skip_validation(:first_name, :last_name, :password) = true

Can someone offer a suggestion for how I might program this? I'd prefer not using an existing gem/library, but am trying to understand how to program this behavior in rails. Thanks.

like image 707
Nathan Avatar asked Nov 14 '22 06:11

Nathan


1 Answers

This could help you define all the setters and checkers dynamically

private 
def self.skip_validations_for(*args)
  
  # this block dynamically defines a setter method 'set_validations' 
  # e.g.
  #   set_validations(true, :email, :last_name, :first_name)
  #   set_validations(false, :email, :last_name, :first_name)
  define_method('set_validations') do |value, *params|
    params.each do |var|
      self.send("skip_#{var.to_s}_validations=", value)
    end
  end

  # this block walks through the passed in fields and defines a checker
  # method `skip_[field_name]_validation?
  args.each do |arg|
    if self.method_defined? arg
      send :define_method, "skip_#{attr.to_s}_validation?" do 
        send "skip_#{attr.to_s}_validation"  
      end
    end
  end
end

Then in your model regardless of whether this is native in the class or included through a module, you can add:

Class Foo < ActiveRecord::Base
  validate :bar, :presence => true
  validate :baz, :length_of => 10

  skip_validations_for :bar, :baz
end

at which point you'll have access to

set_validations(true, :bar, :baz)

and

skip_bar_validation? 

update:

Where would this code go?

This depends on how widely you'd like to use it. If you just want to override the validations in one model then you could put it all directly in that model. If however you wish to create a Module that allows you to do this in every class then you'll have to create a Module put it in the lib/ directory and the make sure it's in the load path. Perhaps this could help you with that.

Why did things change so drastically?

Originally I was using the _validations method on an instance and that is an ActiveRecord method that returns a hash of validations (field name -> ActiveRecord::Validation). That method of doing things automatically turned on the ability for every field. It's more Rails'ish to allow you to opt in the specific fields you want.

like image 97
TCopple Avatar answered Dec 24 '22 06:12

TCopple