What's the cleanest way to override ActiveRecord's find for both models and collections?

后端 未结 3 1684
旧时难觅i
旧时难觅i 2021-02-06 17:31

I have library code that overrides Ar\'s find method. I also include the module for all Association classes so both MyModel.find and @parent.my_models.find work and apply the co

相关标签:
3条回答
  • 2021-02-06 18:12

    You want to override find_every, which is the AR method that will ultimately run find_by_sql with the corresponding query. Overriding find won't work for customized finders, and it's just more fragile.

    But to be compatible with other plugins you can't just overload this method. Instead, alias it and call the original implementation after doing what you want:

    module MyPlugin
      def self.included(base)
        class << base
          alias_method :find_every_without_my_plugin, :find_every
          def find_every(*args)
            # do whatever you need ...
            find_every_without_my_plugin(*args)
          end
        end
      end
    end
    
    ActiveRecord::Base.send :include, MyPlugin
    

    This will enable your plugin for all classes. How do you want to control which models are enabled? Maybe a standard plugin accessor?

    class User < ActiveRecord::Base
      my_plugin
    end
    

    To support this you need to move the class << base to a class method (thus base should be self). Like:

    module MyPlugin
      def self.included(base)
        class << base
          base.extend ClassMethods
        end
      end
    
      module ClassMethods
        def my_plugin
          class << self
            alias_method :find_every_without_my_plugin, :find_every
            # ...
          end
        end
      end
    end
    
    0 讨论(0)
  • 2021-02-06 18:13

    'Pedro's answer is right, but there's a small mistake.

    def self.included(base)
      class << base
        base.extend ClassMethods
      end
    end
    

    should be

    def self.included(base)
      base.extend ClassMethods
    end
    

    Using class << base ... end has the effect of calling 'extend' on 'base' in the class-method scope, but there is no method 'base' in ActiveRecord::Base so an error is thrown. Using base.extend by itself will call the 'extend' method of ActiveRecord::Base.

    0 讨论(0)
  • 2021-02-06 18:14

    First of all, make sure you know Ruby's method call inheritance structure well, as without this you can end up stabbing around in the dark.

    The most straightforward way to do this inside an ActiveRecord class is:

    def self.find(*args)
      super
    end
    

    That will apply to associations as well since they use the base finder themselves. Now you just need to do your customization. The complexity of that can vary widely, and I don't know what you're doing so I can't offer any recommendations.

    Also defining this dynamically will be an exercise unto itself, but this should get you pointed in the right direction.

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