【问题标题】:A more elegant way to skip validations in Rails?在 Rails 中跳过验证的更优雅的方法?
【发布时间】:2012-05-03 16:53:39
【问题描述】:

在我的用户模型中,我通常怀疑电子邮件、名字、姓氏、密码等。

我有几种情况需要跳过全部或部分验证。

目前,我的除非条件类似于:

  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?

当然我也有:

  attr_accessor :skip_first_name_validation, :skip_last_name_validation, etc.

然后我使用私有方法检查每个的状态:

  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..

从那里开始,每当我需要跳过验证时,我只需在我的控制器中为每个人分配一个 true 值。


虽然所有这些都可以正常工作,但我想知道是否有更优雅的方式?

理想情况下,如果我可以对模型中的每个属性使用这样的简单条件,那就太好了:

:skip_validation?

在我的控制器中,只需执行以下操作:

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

有人可以就我如何编程提出建议吗?我不想使用现有的 gem/库,但我想了解如何在 rails 中编程这种行为。谢谢。

【问题讨论】:

  • 这就是 DCI 的全部意义所在。否则,state_machine 不适合吗?
  • 我认为这取决于您如何更新模型。例如,您可以使用 update_column 方法一次更新 1 列,从而跳过验证。
  • 这像 update_attribute 吗?我认为这也不是很干净,因为现在我的控制器会被一系列这些方法调用弄乱。理想情况下,我有一种方法可以将参数传递给控制器​​,例如:skip_validation(用户名、电子邮件、密码等)。坦率地说,我有点惊讶这样的事情不存在带有导轨的 OOTB,但这并不是真正的问题。我想知道如何在 Rails 中进行编程。
  • 取决于您跳过验证的内容?
  • 嗯...我想我在问问题时没有添加足够的清晰度,或者问题太复杂了。

标签: ruby-on-rails validation


【解决方案1】:

这可以帮助您动态定义所有设置器和检查器

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

然后在您的模型中,无论这是在类中是原生的还是通过模块包含的,您都可以添加:

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

  skip_validations_for :bar, :baz
end

此时您可以访问

set_validations(true, :bar, :baz)

skip_bar_validation? 

更新:

这段代码会去哪里?

这取决于您希望使用它的范围。如果您只想覆盖一个模型中的验证,那么您可以将其全部直接放入该模型中。但是,如果您希望创建一个 Module 允许您在每个类中执行此操作,那么您必须创建一个 Module 将其放入 lib/ 目录并确保它位于加载路径中。也许this 可以帮助你。

为什么事情发生了如此巨大的变化?

最初我在一个实例上使用 _validations 方法,这是一个返回验证哈希的 ActiveRecord 方法(字段名称 -> ActiveRecord::Validation)。这种做事的方法自动开启了每个领域的能力。允许您选择所需的特定字段更符合 Rails 风格。

【讨论】:

  • 这是一个好方法 :) 插值 attr.to_s 怎么样?
  • @theodorton 我真的很喜欢你的alias 方法。 :)
  • 嗨@TCopple,我有几个新手问题。 1. 这段代码会去哪里?是否会将其添加到 lib 目录中,我是否需要将其包含在每个模型中? 2.self.class._validators.keys是rails方法吗?我对你描述的最后一种方法有点困惑。你能帮我理解吗?谢谢。
  • @Nathan 我更新了我的解决方案并为您的问题添加了一些答案。我取消了self.class._validators.keys 调用,因为这将为每个字段创建skip_validation 方法,而不仅仅是您选择的那个。一般来说,Rails 更喜欢让合约选择不可用。
  • @TCopple 谢谢 :) 我取消删除了帖子,所以它是开放的。
【解决方案2】:

我不是元编程方面的专家,但我想你可以像这样实现它。

class User < ActiveRecord::Base
  ...
  %w(email first_name last_name).each do |attr|
    self.send :attr_accessor, "skip_#{attr}_validation".to_sym
    self.send :alias, "skip_#{attr}_validation?".to_sym, "skip_#{attr}_validation".to_sym
    self.send :validates_presence_of, attr, { unless: "skip_#{attr}_validation?".to_sym }
  end

  def skip_validation_for(*attrs)
    attrs.map{|attr| send "skip_#{attr}_validation=", true }
  end
end

在模型中混入并不是很优雅,但您可以将其制成一个模块 - 其用途可能类似于:

class User < ActiveRecord::Base
  include SkipValidations
  skip_validations :email, :first_name, :last_name
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    • 2011-10-03
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多