我有一个多态表,我希望能够验证create上提供的实体id是否引用了现有对象。为此,我编写了一个自定义验证器,当我调用find_or_create_by
时会调用它。但是,在验证方法中,我正在执行验证的属性为零。
validate :validate_id, on: :create
...
def validate_id
klass = Object.const_get(entity_type)
return if klass.exists?(entity_id)
errors.add(:entity_id, 'is invalid')
end
entity_type
和entity_id
都是模型成员,并作为参数传递给find_or_create_by
。我不明白为什么在验证方法中它们为零。
这些值在方法中不包含任何内容的原因是,它们的作用域仅限于该方法,而该方法不将它们作为参数。实现您尝试做的事情的最佳方法是编写一个适当的自定义验证器,该验证器继承自ActiveModel::EachValidator
并实现validate_each(record, attribute, value)
方法。
虽然你可以逃脱validates_each
块:
validates_each :entity_id do |record, attr, value|
return if Entity.exists?(value)
errors.add(attr, 'is invalid')
end
然而。。。
为什么不使用普通的包含验证器呢
由于我现在使用的是Rails 4.2,所以当Entity
记录计数足够低,不会导致性能问题时,我就是这样做的
(我会使用更好的错误消息,但为了简单起见,请使用您的错误消息。)
validates :entity_id,
inclusion: { in: proc { Entity.all.pluck(:id) },
message: 'is invalid' }
编辑:我现在看到的问题是,你的多态实体意味着如果不进行一些调整,上面的例子就无法工作,因为Entity
可以是EntityOne
或EntityTwo
两个模型中的任何一个。你可以很容易地检查它们中是否有这样的ID:
validates_each :entity_id do |record, attr, value|
return if EntityOne.exists?(value) || EntityTwo.exists?(value)
errors.add(attr, 'is invalid')
end
或
validates :entity_id,
inclusion: { in: proc { EntityOne.all.pluck(:id) + EntityTwo.all.pluck(:id) },
message: 'is invalid' }
但这并不是一个很好的验证,因为有些无效的情况会被视为有效。
您可能必须求助于一个适当的自定义验证器,该验证器继承自ActiveModel::Validator
并实现validate(record)
方法,该方法也接受options
,您可以在其中传递所需的实体类型。它看起来是这样的:
# Inside the model:
validates_with(EntityIdValidator, expected_entity: entity_type)
#The custom validator:
class EntityIdValidator < ActiveModel::Validator
def validate(record)
expected_entity = options[:expected_entity]
return if expected_entity.exists?(record.entity_id)
record.errors.add(:entity_id, 'is invalid')
end
end