我有一个昂贵的(耗时的)外部请求到我需要做的另一个Web服务,我想缓存它。所以我尝试使用此idiom,将以下内容放在应用程序控制器中:
def get_listings
cache(:get_listings!)
end
def get_listings!
return Hpricot.XML(open(xml_feed))
end
当我在我的控制器中调用get_listings!
时,一切都很酷,但是当我打电话给get_listings
时,Rails抱怨没有给出阻止。当我查找该方法时,我发现它确实期望一个块,另外看起来该方法仅用于视图?所以我猜测虽然没有说明,但这个例子只是伪代码。
所以我的问题是,如何缓存这样的东西?我尝试了其他各种方法,但无法弄明白。谢谢!
答案 0 :(得分:14)
代码内方法看起来像这样:
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插件。
答案 1 :(得分:9)
正如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部分。
答案 2 :(得分: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
答案 3 :(得分: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
它还支持参数级缓存
答案 4 :(得分: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 ||=
的唯一原因
答案 5 :(得分: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
这比上面列出的最简单的方法更好的一点是它会缓存一个零。它具有不需要创建重复方法的便利性。可能宝石方法更清洁。
答案 6 :(得分:0)
迟到了聚会,但万一有人到达这里搜寻。
我经常在项目之间随身携带这个小模块,我发现它足够方便和可扩展,而无需添加额外的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
在这一点上,它仅适用于模型,但可以轻松地进行概括。
答案 7 :(得分:0)
我想推荐我自己的宝石 https://github.com/igorkasyanchuk/rails_cached_method
例如:
class A
def A.get_listings
....
end
end
只需拨打:
A.cached.get_listings