覆盖 ActiveRecord 对模型和集合的查找的最简洁方法是什么?

发布于 2024-07-09 12:08:49 字数 808 浏览 6 评论 0原文

我有重写 Ar 的 find 方法的库代码。 我还包括所有 Association 类的模块,因此 MyModel.find 和 @parent.my_models.find 都可以工作并应用正确的范围。

我的代码基于 will_paginate 的:

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

我的问题是,我只想覆盖某些模型的查找器。 目前我需要扩展所有模型共享的所有关联集合类。 我知道我可以通过传递模块来扩展每个模型的关联:

has_many :things, :extend => SomeCustomMethods

但我的库基本上是 ActiveRecord 插件,所以我想要一个适用于模型和范围集合而不影响应用程序中的所有模型的可插入查找器扩展的干净约定。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冧九 2024-07-16 12:08:49

您想要重写 find_every,这是最终将使用相应查询运行 find_by_sql 的 AR 方法。 重写 find 对于自定义查找器不起作用,而且它更脆弱。

但为了与其他插件兼容,你不能只重载这个方法。 相反,为其添加别名并在执行所需操作后调用原始实现:

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

这将为所有类启用您的插件。 您想如何控制启用哪些模型? 也许是一个标准的插件访问器?

class User < ActiveRecord::Base
  my_plugin
end

为了支持这一点,您需要将 class << base 到类方法(因此 base 应该是 self)。 喜欢:

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

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
白昼 2024-07-16 12:08:49

首先,确保你熟悉Ruby的方法调用继承结构 ,因为如果没有这个,你最终可能会在黑暗中四处乱刺。

在 ActiveRecord 类中执行此操作的最直接方法是:

def self.find(*args)
  super
end

这也适用于关联,因为它们本身使用基查找器。 现在您只需要进行定制即可。 其复杂性可能差异很大,我不知道你在做什么,所以我无法提供任何建议。

此外,动态定义它本身就是一项练习,但这应该能让您指明正确的方向。

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.

长伴 2024-07-16 12:08:49

佩德罗的回答是正确的,但有一个小错误。

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

应该是

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

Using class << base ... end 具有在类方法范围内对“base”调用“extend”的效果,但 ActiveRecord::Base 中没有方法“base”,因此会引发错误。 单独使用base.extend将调用ActiveRecord::Base的'extend'方法。

'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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文