【问题标题】:How do I cache a method with Ruby/Rails?如何使用 Ruby/Rails 缓存方法?
【发布时间】:2011-04-05 21:00:50
【问题描述】:

我需要向另一个 Web 服务发出昂贵(耗时)的外部请求,我想缓存它。所以我尝试使用这个idiom,将以下内容放入应用程序控制器:

def get_listings
  cache(:get_listings!)
end

def get_listings!
  return Hpricot.XML(open(xml_feed))
end

当我在控制器中调用 get_listings! 时,一切都很好,但是当我调用 get_listings 时,Rails 抱怨没有给出块。当我查看该方法时,我发现它确实需要一个块,而且看起来该方法仅用于视图?所以我猜虽然没有说明,但这个例子只是伪代码。

所以我的问题是,我如何缓存这样的东西?我尝试了各种其他方法,但无法弄清楚。谢谢!

【问题讨论】:

    标签: ruby-on-rails ruby caching idioms memoization


    【解决方案1】:

    我想推荐我自己的宝石https://github.com/igorkasyanchuk/rails_cached_method

    例如:

    class A
      def A.get_listings
        ....
      end
    end
    
    

    只要打电话:

    A.cached.get_listings
    

    【讨论】:

      【解决方案2】:

      正如 nruth 所建议的,Rails 的内置缓存存储可能是您想要的。

      试试:

      def get_listings
        Rails.cache.fetch(:listings) { get_listings! }
      end
      
      def get_listings!
        Hpricot.XML(open(xml_feed))
      end
      

      fetch() 检索指定键的缓存值,如果不存在,则将块的结果写入缓存。

      默认情况下,Rails 缓存使用文件存储,但在生产环境中,memcached 是首选。

      更多详情请参阅http://guides.rubyonrails.org/caching_with_rails.html 的第 2 部分。

      【讨论】:

        【解决方案3】:

        聚会迟到了,但以防有人来这里搜索。

        我习惯将这个小模块从一个项目带到另一个项目,我发现它足够方便和可扩展,无需添加额外的 gem。它使用Rails.cache 后端,因此请仅在您有后端时使用。

        # lib/active_record/cache_method.rb
        module ActiveRecord
          module CacheMethod
            extend ActiveSupport::Concern
        
            module ClassMethods
              # To be used with a block
              def cache_method(args = {})
                @caller = caller
                caller_method_name = args.fetch(:method_name)     { @caller[0][/`.*'/][1..-2] }
                expires_in         = args.fetch(:expires_in)      { 24.hours }
                cache_key          = args.fetch(:cache_key)       { "#{self.name.underscore}/methods/#{caller_method_name}" }
        
                Rails.cache.fetch(cache_key, expires_in: expires_in) do
                  yield
                end
              end
            end
        
            # To be used with a block
            def cache_method(args = {})
              @caller = caller
              caller_method_name = args.fetch(:method_name) { @caller[0][/`.*'/][1..-2] }
              expires_in         = args.fetch(:expires_in)  { 24.hours }
              cache_key          = args.fetch(:cache_key)   { "#{self.class.name.underscore}-#{id}-#{updated_at.to_i}/methods/#{caller_method_name}" }
        
              Rails.cache.fetch(cache_key, expires_in: expires_in) do
                yield
              end
            end
          end
        end
        

        然后在初始化器中:

        # config/initializers/active_record.rb
        require 'active_record/cache_method'
        ActiveRecord::Base.send :include, ActiveRecord::CacheMethod
        

        然后在模型中:

        # app/models/user.rb
        class User < AR 
          def self.my_slow_class_method
            cache_method do 
              # some slow things here
            end
          end
        
          def this_is_also_slow(var)
            custom_key_depending_on_var = ...
            cache_method(key_name: custom_key_depending_on_var, expires_in: 10.seconds) do 
              # other slow things depending on var
            end
          end
        end
        

        此时它仅适用于模型,但可以轻松推广。

        【讨论】:

          【解决方案4】:

          有人建议cache_method gem,虽然它很重。如果你需要调用不带参数的方法,解决方法很简单:

          Object.class_eval do
          
            def self.cache_method(method_name)
              original_method_name = "_original_#{method_name}"
              alias_method original_method_name, method_name
              define_method method_name do
                @cache ||= {}
                @cache[method_name] = send original_method_name unless @cache.key?(method_name)
                @cache[method_name]
              end
            end
          
          end
          

          那么你可以在任何类中使用它:

          def get_listings
            Hpricot.XML(open(xml_feed))
          end
          cache_method :get_listings
          

          注意 - 这也会缓存 nil,这是使用它而不是 @cached_value ||= 的唯一原因

          【讨论】:

            【解决方案5】:

            其他答案非常好,但如果您想要一个简单的手动方法,您可以这样做。在您的类中定义如下方法...

            def use_cache_if_available(method_name,&hard_way)
             @cached_retvals ||= {}  # or initialize in constructor
             return @cached_retvals[method_name] if @cached_retvals.has_key?(method_name)
             @cached_retvals[method_name] = hard_way.call
            end
            

            此后,对于您要缓存的每个方法,您可以将方法体包装在类似这样的内容中......

            def some_expensive_method(arg1, arg2, arg3)
              use_cache_if_available(__method__) {
                calculate_it_the_hard_way_here
              }
            end
            

            这比上面列出的最简单的方法做得更好的一点是它会缓存一个 nil。它的方便之处在于不需要创建重复的方法。不过,gem 方法可能更清洁。

            【讨论】:

              【解决方案6】:

              代码内方法可能如下所示:

              def get_listings
                @listings ||= get_listings!
              end
              
              def get_listings!
                Hpricot.XML(open(xml_feed))
              end
              

              它将基于每个请求(每个请求的新控制器实例)缓存结果,尽管您可能希望将“memoize”帮助器视为 api 选项。

              如果您想跨请求共享不要将数据保存在类对象上,因为您的应用不会是线程安全的,除非您擅长并发编程& 确保线程不会干扰彼此对共享变量的数据访问。

              跨请求缓存的“rails 方式”是Rails.cache store。 Memcached 被大量使用,但您可能会发现文件或内存存储符合您的需求。这实际上取决于您的部署方式以及您是否要优先考虑缓存命中、响应时间、存储 (RAM) 或使用托管解决方案,例如一个heroku插件。

              【讨论】:

              • 是的,我肯定想在请求之间缓存它。我曾考虑将其存储在数据库中并手动完成,但我希望有一种更简单的方法。
              • 也许看看 ActiveSupport::Cache::Store 的 docs/api - 可能适合您的需求。我自己没有使用过它,但我确信上面有博客文章或其他使用过它的人,例如使用 memcached。
              【解决方案7】:

              你也可以使用 cachethod gem (https://github.com/reneklacan/cachethod)

              gem 'cachethod'
              

              那么缓存方法的结果非常简单

              class Dog
                cache_method :some_method, expires_in: 1.minutes
              
                def some_method arg1
                  ..
                end
              end
              

              它还支持参数级缓存

              【讨论】:

                【解决方案8】:

                您可以使用cache_method gem:

                gem install cache_method
                require 'cache_method'
                

                在您的代码中:

                def get_listings
                  Hpricot.XML(open(xml_feed))
                end
                cache_method :get_listings
                

                你可能会注意到我删除了get_listings!。如果您需要手动刷新数据的方法,我建议:

                def refresh
                  clear_method_cache :get_listings
                end
                

                这是另一个花絮:

                def get_listings
                  Hpricot.XML(open(xml_feed))
                end
                cache_method :get_listings, (60*60) # automatically expire cache after an hour
                

                【讨论】:

                • 顺便说一下,您应该查看 gem 文档以了解如何配置 gem。提示:CacheMethod.config.storage = Rails.cache
                猜你喜欢
                • 1970-01-01
                • 2011-08-28
                • 1970-01-01
                • 2011-09-08
                • 1970-01-01
                • 1970-01-01
                • 2012-09-24
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多