How to remove validation using instance_eval clause in Rails?

后端 未结 18 1798
情话喂你
情话喂你 2021-02-01 02:11

I would like to enhance existing class using instance_eval. There original definition contains validation, which require presence of certain fields, ie:

class Du         


        
相关标签:
18条回答
  • 2021-02-01 02:50

    Answer by aVenger has problems when you declare validations of more than one attribute in a line:

    validates :name, :message, :presence => true
    

    That's because this line creates a raw_filter with more than one attribute in attributes filter:

    Model.send(:_validate_callbacks)
    => [#<ActiveSupport::Callbacks::Callback:0xa350da4 @klass=Model(...), ... , @raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 @attributes=[:name, :message], @options={}>, @filter="_callback_before_75", @compiled_options="true", @callback_id=76>]
    

    We have to delete the desired attribute from that array and reject the callbacks without attributes

    Dummy.class_eval do
    
      _validators.reject!{ |key, _| key == :field }
    
      _validate_callbacks.each do |callback|
        callback.raw_filter.attributes.delete :field
      end
    
      _validate_callbacks.reject! do |callback|
        callback.raw_filter.attributes.empty? || 
          callback.raw_filter.attributes == [:field]
      end
    end
    

    I have this working on a Rails 3.2.11 app.

    0 讨论(0)
  • 2021-02-01 02:51

    For rails 4.2 (~ 5.0) it can be used the following module with a method:

    module ValidationCancel
       def cancel_validates *attributes
          attributes.select {|v| Symbol === v }.each do |attr|
             self._validators.delete( attr )
    
             self._validate_callbacks.select do |callback|
                callback.raw_filter.try( :attributes ) == [ attr ] ;end
             .each do |vc|
                self._validate_callbacks.delete( vc ) ;end ;end ;end ;end
    

    Note: Since the filtern can be a symbol of an association, or a specific validator, so we have to use #try.

    Then we can use rails-friendly form in a class declaration:

    class Dummy
       extend ValidationCancel
       cancel_validates :field ;end
    

    Note: since removal of the validator is affecting to the whole class and its descendants globally, it is not recommended to use it to remove validations in such way, instead add if clause for the specific rule as follows:

    module ValidationCancel
       def cancel_validates *attributes
          this = self
          attributes.select {|v| Symbol === v }.each do |attr|
             self._validate_callbacks.select do |callback|
                callback.raw_filter.try( :attributes ) == [ attr ] ;end
             .each do |vc|
                ifs = vc.instance_variable_get( :@if )
                ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end
    

    This restricts execution of the validation callback for the specified class and its descendants.

    0 讨论(0)
  • 2021-02-01 02:51

    Assuming the original implementation of Dummy is defined in an engine there is a nasty hack that will do what you want. Define Dummy in your application to keep the original implementation of Dummy from being auto-loaded. Then load the source to Dummy and remove the line that does the validation. Eval the modified source.

    Put the following in your app/models/dummy.rb

    class Dummy < ActiveRecord::Base
    end
    
    # Replace DummyPlugin with name of engine
    engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
    dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
    dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
    eval dummy_source
    

    If it is regular gem instead of an engine the same concept would apply, just would need to load the source for Dummy from the gem root instead of the engine root.

    0 讨论(0)
  • 2021-02-01 02:52

    Wanted to add that, if you're trying to clear validations on a instance of your Model (not the entire model class), don't do my_dummy._validate_callbacks.clear, as that will clear validations on every instance (and future instance) of your Dummy model class.

    For just the instance (and if you wanted to reinstate the validations later), try the following:

    1. Create a copy of the validate callbacks (if you want to reinstate later): my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone

    2. Set the validate callbacks on your instance to empty: my_dummy._validate_callbacks = {}

    3. Do what you want on my_dummy validation free!

    4. Reinstate the callbacks: my_dummy._validate_callbacks = my_dummy_validate_callbacks

    0 讨论(0)
  • 2021-02-01 02:55

    In Rails 4.1,

    I was able to do _validate_callbacks.clear. In my case, I wanted all the validations for a gem removed, so I could create my own. I did this in a module that was patched into the class.

    Module #Name
       extend ActiveSupport::Concern
       included do
            _validate_callbacks.clear
            #add your own validations now
       end
    end
    
    0 讨论(0)
  • 2021-02-01 02:55

    Every Rails validator, pre-defined or custom, is an object, and is expected to respond to #validate(record) method. You can monkey patch or stub this method.

    # MyModel.validators_on(:attr1, :attr2, ...) is also useful
    validator = MyModel.validators.detect do |v|
      validator_i_am_looking_for?(v)
    end
    
    def validator.validate(*_)
      true
    end
    
    # In RSpec you can also consider:
    allow(validator).to receive(:validate).and_return(true)
    

    Tested in Rails 5.1.

    Don't do this unless you understand what you're doing ;)

    0 讨论(0)
提交回复
热议问题