简体   繁体   English

如何使用Ruby / Rails缓存方法?

[英]How do I cache a method with Ruby/Rails?

I have an expensive (time-consuming) external request to another web service I need to make, and I'd like to cache it. 我有一个昂贵的(耗时的)外部请求到我需要做的另一个Web服务,我想缓存它。 So I attempted to use this idiom , by putting the following in the application controller: 所以我试图通过将以下内容放在应用程序控制器中来使用这个习惯用法

def get_listings
  cache(:get_listings!)
end

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

When I call get_listings! 当我打电话给get_listings! in my controller everything is cool, but when I call get_listings Rails complains that no block was given. 在我的控制器中,一切都很酷,但是当我调用get_listings Rails会抱怨没有给出阻止。 And when I look up that method I see that it does indeed expect a block, and additionally it looks like that method is only for use in views? 当我查找该方法时,我发现它确实期望一个块,另外看起来该方法仅用于视图? So I'm guessing that although it wasn't stated, that the example is just pseudocode. 所以我猜测虽然没有说明,但这个例子只是伪代码。

So my question is, how do I cache something like this? 所以我的问题是,如何缓存这样的东西? I tried various other ways but couldn't figure it out. 我尝试了其他各种方法,但无法弄明白。 Thanks! 谢谢!

an in-code approach could look something like this: 代码内方法看起来像这样:

def get_listings
  @listings ||= get_listings!
end

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

which will cache the result on a per-request basis (new controller instance per request), though you may like to look at the 'memoize' helpers as an api option. 它将基于每个请求缓存结果(每个请求的新控制器实例),尽管您可能希望将“memoize”帮助器视为api选项。

If you want to share across requests don't save data on the class objects, as your app will not be threadsafe , unless you're good at concurrent programming & make sure the threads don't interfere with each other's data access to the shared variable. 如果你想在请求之间共享请不要在类对象上保存数据 ,因为你的应用程序不是线程安全的 ,除非你擅长并发编程并确保线程不会干扰彼此对共享的数据访问变量。

The "rails way" to cache across requests is the Rails.cache store . 跨请求缓存的“rails方式”是Rails.cache存储 Memcached gets used a lot, but you might find the file or memory stores fit your needs. Memcached经常使用,但您可能会发现文件或内存存储符合您的需求。 It really depends on how you're deploying and whether you want to prioritise cache hits, response time, storage (RAM), or use a hosted solution eg a heroku addon. 这实际上取决于您的部署方式以及是否要优先考虑缓存命中,响应时间,存储(RAM)或使用托管解决方案,例如heroku插件。

As nruth suggests, Rails' built-in cache store is probably what you want. 正如nruth建议的那样,Rails的内置缓存存储可能就是你想要的。

Try: 尝试:

def get_listings
  Rails.cache.fetch(:listings) { get_listings! }
end

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

fetch() retrieves the cached value for the specified key, or writes the result of the block to the cache if it doesn't exist. fetch()检索指定键的缓存值,或者将块的结果写入缓存(如果它不存在)。

By default, the Rails cache uses file store, but in a production environment, memcached is the preferred option. 默认情况下,Rails缓存使用文件存储,但在生产环境中,memcached是首选选项。

See section 2 of http://guides.rubyonrails.org/caching_with_rails.html for more details. 有关详细信息,请参阅http://guides.rubyonrails.org/caching_with_rails.html的第2部分。

You can use the cache_method gem: 您可以使用cache_method gem:

gem install cache_method
require 'cache_method'

In your code: 在你的代码中:

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

You might notice I got rid of get_listings! 你可能会注意到我摆脱了get_listings! . If you need a way to refresh the data manually, I suggest: 如果您需要一种手动刷新数据的方法,我建议:

def refresh
  clear_method_cache :get_listings
end

Here's another tidbit: 这是另一个小窍门:

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

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

gem 'cachethod'

Then it is deadly simple to cache method's result 然后缓存方法的结果非常简单

class Dog
  cache_method :some_method, expires_in: 1.minutes

  def some_method arg1
    ..
  end
end

It also supports argument level caching 它还支持参数级缓存

There was suggested cache_method gem, though it's pretty heavy. 有人建议使用cache_method gem,尽管它非常重。 If you need to call method without arguments, solution is very simple: 如果需要调用不带参数的方法,解决方案非常简单:

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

then you can use it in any class: 然后你可以在任何类中使用它:

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

Note - this will also cache nil, which is the only reason to use it instead of @cached_value ||= 注意 - 这也会缓存nil,这是使用它而不是@cached_value ||=的唯一原因

Other answers are excellent but if you want a simple hand-rolled approach you can do this. 其他答案非常好,但如果你想要一个简单的手动方法,你可以做到这一点。 Define a method like the below one in your class... 在您的班级中定义类似下面的方法......

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

Thereafter, for each method you want to cache you can put wrap the method body in something like this... 此后,对于要缓存的每个方法,您可以将方法体包装在这样的内容中......

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

One thing that this does better than the simplest method listed above is that it will cache a nil. 这比上面列出的最简单的方法更好的一点是它将缓存为零。 It has the convenience that it doesn't require creating duplicate methods. 它具有不需要创建重复方法的便利性。 Probably the gem approach is cleaner, though. 不过,宝石方法可能比较干净。

Late to the party, but in case someone arrives here searching. 迟到了,但万一有人到这里搜索。

I use to carry this little module around from project to project, I find it convenient and extensible enough, without adding an extra gem. 我用这个小模块从项目到项目,我觉得它很方便和可扩展,没有添加额外的宝石。 It uses the Rails.cache backend, so please use it only if you have one. 它使用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

Then in an initializer: 然后在初始化器中:

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

And then in a model: 然后在一个模型中:

# 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

At this point it only works with models, but can be easily generalized. 此时它只适用于模型,但可以很容易地推广。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM