How to call ActiveRecord validators as instance me

2019-05-16 09:11发布

问题:

I've got a model that needs different validators depending on its current state. How should I go about calling ActiveRecord validators per instance? I'd like to reuse as much plumbing as possible, but I'm not sure how to continue.

class Order < ActiveRecord::Base
  attr_accessible :state

  validate :state_specific_validations

  def state_specific_validations
    if :paid == self.state
      # Warning: here be Unicorns...

      # Wishful thinking...
      validate_presence_of :paid_at
      validate_associated :purchaser

      # Hopeful. What are the validators called internally in Rails?
      errors << PresenceValidator.new(self, :paid_at).valid?
      errors << AssociationValidator.new(self, :paid_at).valid?

      # Plan B
      # ... Hoping for help from the audience ...

    else
      # Even more complicated validator logic, hoping for some DRY validators
    end
  end
end

I could just use custom validators, but why would I need to duplicate all the built-in validator logic (i18n error messages, etc.)?

Is there a neat way of calling the Rails validators as instance methods? I think Sequel's approach of instance-based validators is more reasonable than ActiveRecord's class-based, but I'm not here to judge. I'd just like to get back to solving more interesting problems. I'm just hoping that others have come across this and can point me to some interesting gist or gem.

回答1:

I'm pretty sure all of the validate_* methods can take an :if option - which can point to another method (and likely accept a Proc as well), so you could break up your validations to be more something like:

validates_presence_of :paid_at, :if => :paid?
validates_association :purchaser, :if => :paid?

To clean things up further, there's the with_options helper:

with_options :if => :paid? do |v|
  v.validates_presence_of :paid_at
  v.validates_association :purchaser
end

Not sure if either can be used with a standard validate :custom_validate_method though - but it wouldn't surprise me.



回答2:

Is there any reason why this is inappropriate? It seems like it might work, but maybe metaprogramming warped my brain...

class Order < ActiveRecord::Base
  attr_accessible :state

  validate :state_specific_validations

  def state_specific_validations
    if :paid == self.state
      class << self
        validate_presence_of :paid_at
        validate_associated :purchaser
      end
    end
  end
end

Worst part is that tests are passing, so I'm not sure if I solved it or I need better tests. For example, I'm not 100% positive this singleton modification does not affect other Orders.

sigh Need some sleep.