问题
Given a model
class BaseModel < ActiveRecord::Base
validates_presence_of :parent_id
before_save :frobnicate_widgets
end
and a derived model (the underlying database table has a type
field - this is simple rails STI)
class DerivedModel < BaseModel
end
DerivedModel
will in good OO fashion inherit all the behaviour from BaseModel
, including the validates_presence_of :parent_id
. I would like to turn the validation off for DerivedModel
, and prevent the callback methods from firing, preferably without modifying or otherwise breaking BaseModel
What's the easiest and most robust way to do this?
回答1:
I like to use the following pattern:
class Parent < ActiveRecord::Base
validate_uniqueness_of :column_name, :if => :validate_uniqueness_of_column_name?
def validate_uniqueness_of_column_name?
true
end
end
class Child < Parent
def validate_uniqueness_of_column_name?
false
end
end
It would be nice if rails provided a skip_validation method to get around this, but this pattern works and handles complex interactions well.
回答2:
As a variation of the answer by @Jacob Rothstein, you can create a method in parent:
class Parent < ActiveRecord::Base
validate_uniqueness_of :column_name, :unless => :child?
def child?
is_a? Child
end
end
class Child < Parent
end
The benefit of this approach is you not need to create multiple methods for each column name you need to disable validation for in Child class.
回答3:
From poking around in the source (I'm currently on rails 1.2.6), the callbacks are relatively straightforward.
It turns out that the before_validation_on_create
, before_save
etc methods, if not invoked with any arguments, will return the array which holds all the current callbacks assigned to that 'callback site'
To clear the before_save ones, you can simply do
before_save.clear
and it seems to work
回答4:
A cleaner way is this one:
class Parent < ActiveRecord::Base
validate :column_name, uniqueness: true, if: 'self.class == Parent'
end
class Child < Parent
end
Or you can use it also in this way:
class Parent < ActiveRecord::Base
validate :column_name, uniqueness: true, if: :check_base
private
def check_base
self.class == Parent
end
end
class Child < Parent
end
So, uniqueness validation is done if the instance class of model is Parent
.
- Instance class of
Child
isChild
and differs fromParent
. - Instance class of
Parent
isParent
and is the same asParent
.
回答5:
Since rails 3.0 you can also access the validators
class method to manipulate get a list of all validations. However, you can not manipulate the set of validations via this Array.
At least as of rails 5.0 you however seem to be able to manipulate the _validators
(undocumented) method.
Using this method you can modify the validations in the subclass like e.g.:
class Child < Parent
# add additional conditions if necessary
_validators.reject! { |attribute, _| attribute == :parent_id }
end
While this uses an undocumented method, is has the benefit of not requiring the superclass to know anything about the child's implementation.
回答6:
Again poking around in the source, it seems that validations can be run either on every save, or updates/creates only. This maps to
:validate
=> all saves:validate_on_create
=> creations only:validate_on_update
=> updates only
To clear them, you can use write_inheritable_attribute
, like this:
write_inheritable_attribute :validate, nil
回答7:
Here is a slight variation of RubyDev's that I've been using in mongoid 3.
class Parent
include Mongoid::Document
validates :column_name , uniqueness: true, unless: Proc.new {|r| r._type == "Child"}
end
class Child < Parent
end
It has been working pretty good for me so far.
来源:https://stackoverflow.com/questions/279478/how-can-i-disable-a-validation-and-callbacks-in-a-rails-sti-derived-model