Rails ActiveRecord:验证单个属性

41

我是否可以验证ActiveRecord中的单个属性?

就像这样:

ac_object.valid?(attribute_name)
5个回答

47
有时候会有一些验证比较耗费资源(例如需要执行数据库查询的验证)。这种情况下,你需要避免使用valid?,因为它会做更多不必要的事情。
有一个替代方案。你可以使用ActiveModel::Validationsvalidators_on方法。
根据这个方法,你可以手动验证你想要验证的属性。
例如,我们只想验证Posttitle
class Post < ActiveRecord::Base

  validates :body, caps_off: true 
  validates :body, no_swearing: true
  validates :body, spell_check_ok: true

  validates presence_of: :title
  validates length_of: :title, minimum: 30
end

no_swearingspell_check_ok 是非常昂贵的复杂方法时,我们可以采取以下措施:

def validate_title(a_title)
  Post.validators_on(:title).each do |validator|
    validator.validate_each(self, :title, a_title)
  end
end

这将仅验证标题属性而不调用任何其他验证。

p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]

注意

我不完全确定我们是否应该安全地使用validators_on,因此我建议在validates_title中以合理的方式处理异常。


4
使用 self.class.validators_on 并将属性名称作为参数,使其更具可重用性。 - thisismydesign

46
你可以在你的模型中实现自己的方法。类似这样:
def valid_attribute?(attribute_name)
  self.valid?
  self.errors[attribute_name].blank?
end

或者将它添加到 ActiveRecord::Base


8
如果您正在使用某些表单构建器,这种方法可能会出现问题。例如,您可能会开始在根本没有输入内容的字段上显示错误信息。 - Stan Bright
1
可以运行valid?并清除所有不在相关属性中的错误。这在某些情况下可能有效。 - radiospiel
1
这个做法完全相反:它在后台验证所有内容。对于给定的用例来说,这是一个相当糟糕的做法,因为它会针对每个属性验证整个对象。 - thisismydesign
我可以在控制器中使用这个方法吗? - Mayuresh Srivastava

12

我最终在 @xlembouras 的回答基础上构建,并将此方法添加到我的 ApplicationRecord 中:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate_each(self, attribute, send(attribute))
      end
    end
    errors.none?
  end
end

然后我可以在控制器中做这样的事情:

if @post.valid_attributes?(:title, :date)
  render :post_preview
else
  render :new
end

3
有个问题:errors.none?可能涉及其他错误(例如,如果此函数之前针对不同的属性运行过)。可能的改进:如果您只关心布尔标志而不是实际错误,则可以考虑在循环中返回:return false unless self.errors[attribute].blank?。我认为self[attribute]send(attribute)更好,但这只是个人喜好。另外,您可以将其提取到concern中,而不是在父类中拥有它(再次说明偏好)。 - thisismydesign
1
如果您想要返回指定列的状态(仅对指定列运行验证),我建议在调用none?之前对errors进行切片:errors.slice(*attributes).none?。不过,这不是ActiveModel::Validations通常处理事情的方式。值得一提的是,使用[]访问属性与使用send不同。后者将调用访问器方法,这正是您99/100所需要的。此外,短路意味着并非所有错误都可供视图呈现。这可能是不可取的。 - coreyward

1

在@coreyward的回答基础上,我还添加了一个validate_attributes!方法:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate_each(self, attribute, send(attribute))
      end
    end
    errors.none?
  end

  def validate_attributes!(*attributes)
    valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
  end
end

1

由于validator.validate_each是一个私有方法,所以我使用其他解决方案时遇到了问题。

这是在Rails 6下工作的一段代码:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate(self)
      end
    end
    errors.none?
  end
end

使用validator.validate(self)而不是...validate_each的一个好处是它会考虑到allow_nilallow_blank选项。https://github.com/rails/rails/blob/ae02cd6539d4768718ca7c05201f4514b9f068e9/activemodel/lib/active_model/validator.rb#L150 - Thimo Jansen
使用 validate 的一个副作用是它可能会验证超出你范围的字段。例如:validates :email, :first_name, presence: true 并调用 valid_attributes?(:email),这将同时验证 first_name - Thimo Jansen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接