【问题标题】:How to call expire_fragment from Rails Observer/Model?如何从 Rails 观察者/模型调用 expire_fragment?
【发布时间】:2010-09-28 10:45:22
【问题描述】:

我几乎尝试了所有方法,但似乎无法使用 来自模型的 expire_fragment?我知道你不应该这样做 非 MVC,但肯定有很多方法可以做到这一点。

我在 lib/cache_helper.rb 中创建了一个带有我所有过期助手的模块, 每一个都只是一堆 expire_fragment 调用。我有我的所有 在 /app/sweepers 下设置缓存清扫器并具有“包括 CacheHelper”在我的应用程序控制器中,所以缓存中的过期 通过控制器调用应用程序时工作正常。

然后事情是我有一些外部守护进程,尤其是一些 重复的 cron 任务,它调用一个调用某个特定的 rake 任务 方法。此方法进行一些处理并将条目输入到 模型,之后我需要使缓存过期。

最好的方法是什么,因为我无法在模型中指定缓存清扫器。 直截了当的观察者似乎是最好的解决方案,但它 抱怨 expire_fragment 未定义等,我什至 尝试将 ActionController 缓存类包含到观察者中 但这没有用。我想要一些关于如何创建解决方案的想法 为了这。谢谢。

【问题讨论】:

    标签: ruby-on-rails ruby caching memcached


    【解决方案1】:

    免责声明:我的铁轨有点生锈,但这个或类似的东西应该可以工作

    ActionController::Base.new.expire_fragment(key, options = nil) 
    

    【讨论】:

    • 有人试过这个吗?我手边没有导轨,但我很确定它会解决问题...
    • 我在 Rails 3.0.0.rc 中试过了,效果很好,谢谢!它不会删除 /tmp/cache 目录中的文件夹层次结构,但会删除缓存文件,这就是它需要做的。
    • 这在 Rails 2.3 中不起作用 - 可能是由于插件。从清扫器中执行上述行时,它会显示“NoMethodError(nil:NilClass 的未定义方法 `rewrite')”。对“重写”方法的调用超出了我的范围——我的应用程序没有定义这样的函数——我正在使用的任何插件也没有。
    • 我在 Rails 3.1 rc4 中收到以下错误:NoMethodError: undefined method 'host' for nil:NilClass from "actionpack-3.1.0.rc4/lib/action_controller/metal/url_for.rb:30:in 'url_options'". 我猜想新控制器实例中缺少请求对象。有什么建议吗?
    • 在 rails 3.1.1, ruby​​ 1.9.2-p290 上像魅力一样工作
    【解决方案2】:

    Orion 提供的解决方案完美运行。作为增强和方便起见,我将以下代码放入config/initializers/active_record_expire_fragment.rb

    class ActiveRecord::Base
      def expire_fragment(*args)
        ActionController::Base.new.expire_fragment(*args)
      end
    end
    

    现在,您可以在 ActiveRecord::Base 的所有实例上使用 expire_fragment,例如User.first.expire_fragment('user-stats')

    【讨论】:

    • 这是最流畅的方法,恕我直言,经常用不同的助手做类似的事情
    【解决方案3】:

    这很容易做到。您可以实现 Orion 的建议,但您也可以实现下面说明的更广泛的技术,它使您可以从任何模型访问当前控制器,以及您决定打破 MVC 分离的任何目的(例如,弄乱片段缓存,访问 @987654321 @,生成路径/URL等)

    为了从 any 模型获得对当前请求的控制器(如果有)的访问权限,请将以下内容添加到environment.rb 或,最好是一个新插件(例如创建包含以下代码的vendor/plugins/controller_from_model/init.rb):

    module ActiveRecord
      class Base
        protected
          def self.thread_safe_current_controller #:nodoc:
            Thread.current[:current_controller]
          end
    
          def self.thread_safe_current_controller=(controller) #:nodoc:
            Thread.current[:current_controller] = controller
          end
    
          # pick up the correct current_controller version
          #  from @@allow_concurrency
          if @@allow_concurrency
            alias_method :current_controller,  :thread_safe_current_controller
            alias_method :current_controller=, :thread_safe_current_controller=
          else
            cattr_accessor :current_controller
          end
      end
    end
    

    然后,在app/controllers/application.rb

    class ApplicationController < ActionController::Base
      before_filter { |controller|
        # all models in this thread/process refer to this controller
        #  while processing this request
        ActiveRecord::Base.current_controller = controller
      }
    
      ...
    

    然后,来自任何模型

    if controller = ActiveRecord::Base.current_controller
      # called from within a user request
    else
      # no controller is available, didn't get here from a request - maybe irb?
    fi
    

    无论如何,在您的特定情况下,您可能希望在相关控制器类加载时将代码注入您的各种 ActiveRecord::Base 后代,以便实际的控制器感知代码仍驻留在 app/controllers/*.rb 中,但这不是强制性的这样做是为了获得一些功能性的东西(虽然丑陋且难以维护。)

    玩得开心!

    【讨论】:

    • 谢谢!为了让它工作,我不得不将代码放入 environment.rb (它在插件中不起作用)并将 @@allow_concurrency 更改为 ActionController::Base.allow_concurrency (我希望是同一件事)。
    • 开始拍脚吧!
    【解决方案4】:

    在我的一个脚本中,我使用了以下 hack:

      require 'action_controller/test_process'
    
      sweepers = [ApartmentSweeper]
    
      ActiveRecord::Base.observers = sweepers
      ActiveRecord::Base.instantiate_observers
    
      controller = ActionController::Base.new
      controller.request = ActionController::TestRequest.new
      controller.instance_eval do
        @url = ActionController::UrlRewriter.new(request, {})
      end
    
      sweepers.each do |sweeper|
        sweeper.instance.controller = controller
      end
    

    然后,一旦调用了 ActiveRecord 回调,清扫器就可以调用 expire_fragment。

    【讨论】:

    • Maurycy,感谢您的示例和 sn-p,我会试一试。这是我可以贴在模型中的东西吗?
    • 这是一个很棒的技巧,我不会称其为 hack。您正在为清扫器设置足够的状态来完成其工作,并让其他一切正常工作。
    • 附带说明:我需要将“controller.request = ActionController::TestRequest.new”替换为“controller.request = request”,否则清扫器会尝试使不存在的缓存过期主机。幸运的是,“请求”对象可以通过清扫器获得。
    【解决方案5】:

    我是一个 Rails 菜鸟,所以这可能不正确,甚至没有帮助,但尝试从模型中调用控制器操作似乎是错误的。

    是否不可能在控制器中编写执行您想要的操作,然后从您的 rake 任务中调用控制器操作?

    【讨论】:

    • 如果您从控制器外部修改数据库记录,您需要以某种方式使缓存视图无效,例如当 cron 作业将数据导入数据库时​​。 Rails 在这里缺少一些重要的粘合剂。缓存清扫器仅在控制器上运行操作时运行。那么在控制器之外采取的“行动”,即后台作业呢?应该也可以在这里安装扫地机。
    【解决方案6】:

    为什么不让您的外部 rake 任务调用控制器上的 expiry 方法。那么你仍然是 MVC 兼容的,你不会依赖于一些范围 hack 等等。

    就此而言,您为什么不将所有守护程序/外部功能放在控制器上并让 rake/cron 调用它。它会更容易维护。

    -- MarkusQ

    【讨论】:

    • 这有点好笑,但没有建设性。
    【解决方案7】:

    将当前控制器作为参数传递给模型方法调用不是更容易和更干净吗?如下:

    def delete_cascade(controller)
    
      self.categories.each do |c|
        c.delete_cascade(controller)
        controller.expire_fragment(%r{article_manager/list/#{c.id}.*})                
      end
      PtSection.delete(self.id)
      controller.expire_fragment(%r{category_manager/list/#{self.id}.*})        
    end
    

    您可以从模型中访问控制器的所有公共方法和属性。 只要不修改控制器的状态就可以了。

    【讨论】:

      【解决方案8】:

      这可能不适用于您正在做的事情,但您可以在模型上定义自定义回调:

      class SomeModel < ActiveRecord::Base
          define_callback :after_exploded
      
          def explode
              ... do something that invalidates your cache ...
              callback :after_exploded
          end
      end
      

      然后您就可以像往常一样使用扫地机了:

      class SomeModelSweeper < ActionController::Caching::Sweeper
        observe SomeModel 
      
          def after_exploded(model)
            ... expire your cache
          end
      end
      

      让我知道这是否有用!

      【讨论】:

        猜你喜欢
        • 2012-03-13
        • 1970-01-01
        • 2010-12-25
        • 2019-08-22
        • 2016-02-20
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多