I have a polymorphic table, and I want to be able to verify that the entity id provided on create does in fact reference an existing object. I wrote a custom validator to do just this, which is invoked when I call find_or_create_by
. However, the attributes that I am performing validation on are nil in the validation method.
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
and entity_id
are both model members, and are passed as args to find_or_create_by
. I don't understand why they are nil in the validation method.
The reason those values don't hold anything in your method is because they are only scoped to that method, and that method does not take them in as arguments. The best way to accomplish what you're trying to do is to write a proper custom validator that inherits from ActiveModel::EachValidator
and implements the validate_each(record, attribute, value)
method.
Although you can get away with a validates_each
block:
validates_each :entity_id do |record, attr, value|
return if Entity.exists?(value)
errors.add(attr, 'is invalid')
end
However...
Why not just use a normal inclusion validator?
Since I'm on Rails 4.2 right now this is how I do it when Entity
record counts are low enough for it not to cause performance issues.
(I'd use a better error message, but using yours for simplicity.)
validates :entity_id,
inclusion: { in: proc { Entity.all.pluck(:id) },
message: 'is invalid' }
Edit: I see now the issue is that your polymorphic entity means the examples above won't work without some tweaking since Entity
could be either of two models EntityOne
or EntityTwo
. You could easily check if either of them contain the ID in question like this:
validates_each :entity_id do |record, attr, value|
return if EntityOne.exists?(value) || EntityTwo.exists?(value)
errors.add(attr, 'is invalid')
end
Or
validates :entity_id,
inclusion: { in: proc { EntityOne.all.pluck(:id) + EntityTwo.all.pluck(:id) },
message: 'is invalid' }
But this isn't a great validation since there are invalid situations passed through as valid.
You might have to resort to a proper custom validator that inherits from ActiveModel::Validator
and implements the validate(record)
method which also accepts options
where you can pass in the type of entity expected. It would look like this:
# 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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.