【问题标题】:Re-open an ActiveRecord model that's provided by a gem重新打开 gem 提供的 ActiveRecord 模型
【发布时间】:2012-06-01 10:16:49
【问题描述】:

我正在尝试扩展 gem (https://github.com/peteonrails/vote_fu) 为我的应用程序提供的 ActiveRecord 模型 (Vote)。 (即,app/models 中没有 vote.rb

我的第一个方法是创建一个名为 lib/extend_vote.rb 的文件,其中包含代码:

Vote.class_eval do
  after_create :create_activity_stream_event
  has_one :activity_stream_event

  def create_activity_stream_event
    # something..
  end
end

这在创建第一个投票时有效,但是当我尝试创建每个后续投票时,我收到错误 TypeError (can't dup NilClass)

我认为这个错误是由于Vote 类在每次请求后自动重新加载,但lib/extend_vote.rb 中的代码仅在服务器启动时加载一次,这会导致has_one :activity_stream_event 关联行为奇怪。 (另外,如果我在development.rb 中设置config.cache_classes = true,问题就会消失)

为了解决这个问题,我尝试通过将to_prepare 块添加到我的development.rb 来使投票扩展在每个请求上重新加载:

config.to_prepare do
  load 'extend_vote.rb'
end

这解决了(can't dup NilClass) 问题,但现在每当我创建一个新投票时,create_activity_stream_event 回调就会被调用一次。即,第一次投票调用它一次,第二次调用它两次,等等。所以看起来to_prepare 块正在积极地重新加载扩展并添加重复的回调。

向这个Vote 模型添加方法和回调的最佳方式是什么?

【问题讨论】:

  • 如果你只使用class Vote 而不是Vote.class_eval 是否有效?您还可以做的一件事是在 gem 本身中编辑代码,然后使用修改后的版本。
  • class Vote 的行为与Vote.class_eval 相同——两者都不起作用。我想我可以修改 gem,但我真的不想大声笑。真是一团糟!
  • 您认为为什么会重新加载 Vote 类?在 resposity 中,该类位于 lib 目录中,因此与您相同...

标签: ruby-on-rails model gem metaprogramming


【解决方案1】:

[更新:应该是防止模块在同一个类中多次包含的正确解决方案]

我相信您可以使用ActiveSupport::Concern 来防止模块被多次包含,从而导致回调被多次调用。 请参阅下面的示例:

module VotePatch
  extend ActiveSupport::Concern

  included do
    after_create :create_activity_stream_event
    has_one :activity_stream_event
  end

  module InstanceMethods
    def create_activity_stream_event
      #your code here
    end  
  end

end

Vote.send(:include, VotePatch)

【讨论】:

  • 更新了我的答案,我相信 ActiveSupport::Concern 会解决你的问题
【解决方案2】:

你能试试这样的吗:

class Vote
    after_create :create_activity_stream_event
    has_one :activity_stream_event

    def create_activity_stream_event
        # something..
    end
end

我认为它会添加你的函数并调用函数“after_create”和“has_hone”。

【讨论】:

  • 对不起,我没有看到 agmcleod 的评论。这可能是同样的想法......它应该有效,但它没有。
  • 嘿,至少你没有像我一样投反对票……而且我们的建议是一样的!大声笑
  • 这可能是因为你说要把它放在初始化器中。或者,可能是,我在你之后说。
【解决方案3】:

Adrien Coquio 对ActiveSupport::Concerns 提出了正确的想法,即the Rails way 来扩展模型。他的代码可以工作,你应该使用它。

但是,这在开发过程中不会一直有效,因为当 Rails 在文件更改时重新加载您的类时,它不会重新评估 #send 行。我能找到的唯一解决方案是在生产中附加ActionDispatch 回调,以确保在每次页面加载后重新需要该文件:

if Rails.env.development?
  ActionDispatch::Callbacks.to_prepare do
    require_dependency "../../lib/vote_fu_extensions"
  end
end

在生产中,或者如果您在配置中将 cache_classes 设置为 true,则不需要这样做。

【讨论】:

    【解决方案4】:

    您的问题可能是由于您正在修补课程。当 rails 尝试重新加载常量时 它没有考虑您的文件。

    尝试使用下面给出的模块技术。

    添加一个名为lib/vote_fu_extension.rb的文件

    module VoteFuExtension
      def self.included(base)
        base.has_one :activity_stream_event
        base.after_create :create_activity_stream_event
      end
      def create_activity_stream_event
        # something..
      end  
    end
    Vote.send(:include, VoteFuExtension)
    

    添加一个名为config/initializers/vote_fu.rb的初始化程序

    require "vote_fu_extension"
    

    注意

    如果您想将类方法添加到 Vote 模型,请参阅此 answer

    无耻插件:我的vote_fu gem 中的fork 有一些新功能和增强功能。

    【讨论】:

      【解决方案5】:

      请注意:这是一个非常古老的 gem(最后一次提交是 3 年),从外观上看,它不适用于 rails 3.x。在 Rails 3.x 引擎中,这种事情变得更容易了。

      据我了解,第一种情况的问题不是重新加载投票模型(它不应该),而是重新加载 activity_stream_event 模型。因为没有重新加载投票模型,所以关联保留在重新加载之前的 activity_stream_event 类的版本上。由于 rails 在重新加载类之前会删除类,这会导致问题。

      有了这个,试试这个技巧:

      #in config/initializers/abstract_vote.rb
      AbstractVote = Vote
      AbstractVote.abstract_class = true
      Object.send :remove_const, :Vote
      
      #in app/models/vote.rb
      
      class Vote < AbstractVote
        after_create :create_activity_stream_event
        has_one :activity_stream_event
      
        def create_activity_stream_event
        end
      end
      

      这样做的目的是让您拥有自己的 Vote 类,该类继承自 gem 中的类。

      但是,我再次敦促您找到更新的东西或自己动手制作(宝石只有约 250 行红宝石)

      【讨论】:

        【解决方案6】:

        我会尝试 agmcleod 在 cmets 中的建议,但不是将其放入 lib,而是将其放入 config/initializers/vote.rb

         class Vote
           after_create :create_activity_stream_event
           has_one :activity_stream_event
        
           def create_activity_stream_event
           # something..
           end
         end
        

        当然,您可以分叉 gem,进行修改并链接到您的 Gemfile 中的分叉版本(这是我的偏好)。

        【讨论】:

        • 为什么是config/initializers 而不是lib?我假设我需要将load 语句保留在to_prepare 块中?
        • 不应该不需要该加载语句,因为初始化程序文件中的项目在所有环境的启动时都会加载一次。
        • 这不起作用 - 如果我将 load 留在 to_prepare 块中,我会收到与文件位于 lib 时相同的错误。如果我删除 load,那么我会收到 (can't dup NilClass) 错误
        • 说真的,你真的反对我的建议吗?!!哇。很抱歉尝试提供帮助,但这对我来说对我曾经像这样修补过的每一颗宝石都很好。您也可以尝试分叉 gem 的其他建议 - 但如果您只是要为此惩罚我...天哪。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-26
        • 1970-01-01
        • 2014-02-21
        • 1970-01-01
        • 2017-03-08
        相关资源
        最近更新 更多