如何使用 Rails.cache.fetch 和 memcache 存储 nil?

How to store nil with Rails.cache.fetch & memcache?

我有一个现有的模型单例方法触发昂贵的数据库查询,我想为缓存建模。为此,我围绕有问题的方法包装了一个 Rails.cache.fetch() 调用:

# app/models/specialist.rb
class Specialist < ActiveRecord::Base

  def city
    Rails.cache.fetch([self.class.name, self.id, "city"], expires_in: 23.hours) do

      # expensive legacy query:
      if responded?
        o = offices.first
        return nil if o.blank?
        return o.city
      elsif hospital_or_clinic_only?
        (hospitals.map{ |h| h.city } + clinics.map{ |c| c.cities }).flatten.reject{ |i| i == nil }.uniq.first
      elsif hospital_or_clinic_referrals_only?
        (offices.map{ |o| o.city } + hospitals.map{ |h| h.city } + clinics.map{ |c| c.cities }).flatten.reject{ |c| c.blank? }.uniq.first
      else
        nil
      end

    end
  end

end

对所有记录执行 .city 过去需要 16 秒;有了这个 Rails.cache.fetch 块,它只下降到 7 秒,因为一半的记录仍在触发数据库调用。

当我调查时我发现当 city 方法 returns nil, Rails.cache 没有将结果写入 memcache -- 这意味着我的专家记录的一半仍然尽管 "cached"

仍会触发昂贵的数据库查找

如何在使用 memcache 时强制 Rails.cache.fetch 存储 nil 的值,以便不会触发再次查找 nil 的另一个数据库查找?

一种解决方案是使用 NullObject,如下所示:

class Specialist
  NullData = Struct.new(nil)

  def city
    result = Rails.cache.fetch([self.class.name, self.id, "city"], expires_in: 23.hours) do
      if responded?
        ..
      else
        NullData.new()
      end
     result.is_a?(NullData) ? nil : result 
   end
end

这样您就可以创建一个可以缓存的空对象。然后当你 return 你检查它是否是一个已存储的空对象,在这种情况下你 return nil (以确保你不会破坏依赖于你的方法的旧代码 returning nil),否则你 return 缓存的内容。