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

发布于 2024-09-16 17:37:35 字数 486 浏览 8 评论 0原文

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

def get_listings
  cache(:get_listings!)
end

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

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

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

I have an expensive (time-consuming) external request to another web service I need to make, and I'd like to cache it. 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! in my controller everything is cool, but when I call get_listings Rails complains that no block was given. 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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

爱情眠于流年 2024-09-23 17:37:35

代码内方法可能看起来像这样:

def get_listings
  @listings ||= get_listings!
end

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

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

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

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

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.

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. Memcached gets used a lot, but you might find the file or memory stores fit your needs. 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 e.g. a heroku addon.

夏了南城 2024-09-23 17:37:35

正如 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 节。

As nruth suggests, Rails' built-in cache store is probably what you want.

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.

By default, the Rails cache uses file store, but in a production environment, memcached is the preferred option.

See section 2 of http://guides.rubyonrails.org/caching_with_rails.html for more details.

怎会甘心 2024-09-23 17:37:35

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

You can use the 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!. 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
饭团 2024-09-23 17:37:35

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

它还支持参数级缓存

You can also use 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

心舞飞扬 2024-09-23 17:37:35

有人建议使用 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 ||=< 的唯一原因/代码>

There was suggested cache_method gem, though it's pretty heavy. 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 ||=

梦途 2024-09-23 17:37:35

聚会迟到了,但以防万一有人来这里寻找。

我曾经在一个项目之间携带这个小模块,我发现它足够方便且可扩展,无需添加额外的 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

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

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.

# 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.

冷默言语 2024-09-23 17:37:35

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

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

这比上面列出的最简单的方法做得更好的一件事是它会缓存一个零。它的优点是不需要创建重复的方法。不过,宝石方法可能更干净。

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.

凉墨 2024-09-23 17:37:35

我想建议我自己的 gem https://github.com/igorkasyanchuk/rails_cached_method

例如:

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

只需拨打:

A.cached.get_listings

I'd like to suggest my own gem https://github.com/igorkasyanchuk/rails_cached_method

For example:

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

Just call:

A.cached.get_listings
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文