14

我需要向另一个 Web 服务发出昂贵(耗时)的外部请求,并且我想缓存它。因此,我尝试通过将以下内容放入应用程序控制器中来使用此习惯用法:

def get_listings
  cache(:get_listings!)
end

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

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

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

4

8 回答 8

18

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

def get_listings
  @listings ||= get_listings!
end

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

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

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

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

于 2010-08-30T17:07:18.980 回答
12

正如 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 部分。

于 2010-08-31T15:24:29.290 回答
3

您可以使用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
于 2011-03-17T01:41:09.840 回答
1

您还可以使用 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

它还支持参数级缓存

于 2014-06-09T23:33:44.307 回答
1

有建议cache_method的宝石,虽然它很重。如果您需要调用不带参数的方法,解决方案非常简单:

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 ||=

于 2017-02-03T18:35:22.360 回答
1

派对迟到了,但万一有人来这里搜索。

我习惯将这个小模块从一个项目带到另一个项目,我发现它足够方便和可扩展,无需添加额外的 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

在这一点上,它只适用于模型,但可以很容易地推广。

于 2018-12-19T14:37:53.653 回答
0

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

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。它的便利之处在于它不需要创建重复的方法。不过,宝石方法可能更清洁。

于 2016-02-15T04:28:42.697 回答
0

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

例如:

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

只需致电:

A.cached.get_listings
于 2021-06-23T14:24:35.093 回答