问题
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 correct scope.
I based my code off of will_paginate's:
a = ActiveRecord::Associations
returning([ a::AssociationCollection ]) { |classes|
# detect http://dev.rubyonrails.org/changeset/9230
unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
classes << a::HasManyThroughAssociation
end
}.each do |klass|
klass.send :include, Finder::ClassMethods
klass.class_eval { alias_method_chain :method_missing, :paginate }
end
My problem is, I only want to override the finders for some models. Currently I need to extend all association collection classes which are shared by all models. I know I can extend associations per model by passing a module:
has_many :things, :extend => SomeCustomMethods
But my library is basically am ActiveRecord plugin, so I'd like a clean convention for plugable finder extensions that apply to both the model and scoped collections without affecting all models in the application.
回答1:
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
回答2:
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.
回答3:
'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.
来源:https://stackoverflow.com/questions/314786/whats-the-cleanest-way-to-override-activerecords-find-for-both-models-and-coll