You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails底层缓存:当ActiveRecord对象updated_at变更或集合新增对象时更新缓存

实现ActiveRecord对象与集合变更时的底层缓存更新

我来给你拆解一下怎么实现这个需求——让底层缓存能感知ActiveRecord对象的updated_at变更,还有集合新增对象时自动更新。其实核心思路和片段缓存的基于键的过期策略是通的:把所有会影响缓存内容的变化因子都融入缓存键,这样一旦因子变化,缓存键就会更新,旧缓存自然失效,Rails会自动生成新的缓存内容。

1. 单个对象的缓存更新:绑定updated_at

当单个ActiveRecord对象的updated_at变更时,我们只需要把对象本身纳入缓存键即可——Rails的ActiveRecord对象自带cache_key方法,这个方法会自动把对象的id和updated_at拼接成唯一键,完美适配基于时间戳的过期需求。

举个实际例子,假设Product模型有一个耗时的计算方法discounted_price,我们用底层缓存来优化:

class Product < ApplicationRecord
  def discounted_price
    Rails.cache.fetch([self, "discounted_price"]) do
      # 这里是你的耗时计算逻辑,比如从第三方接口拉取折扣率再计算
      price * fetch_discount_rate
    end
  end
end

只要Product的updated_at一改变,[self, "discounted_price"]生成的缓存键就会变化,下次调用discounted_price时就会重新计算并缓存新值,完全不用手动删除旧缓存。

如果你想更手动控制键的生成,也可以直接拼接updated_at:

Rails.cache.fetch(["product_#{id}_discounted_price", updated_at.to_i]) do
  price * fetch_discount_rate
end

2. 集合的缓存更新:感知新增/变更

对于集合(比如所有在售商品列表),我们需要覆盖两种变化:集合内对象的更新、新增/删除对象。这时候可以利用Rails集合的cache_key特性——它会自动包含集合的记录数量和所有记录中最大的updated_at,只要其中一个变化,缓存键就会更新。

比如缓存所有活跃商品的名称和价格列表:

class Product < ApplicationRecord
  def self.active_products_summary
    active_products = where(status: :active)
    Rails.cache.fetch([active_products, "active_products_summary"]) do
      active_products.pluck(:name, :price)
    end
  end
end

这样一来:

  • 当新增一个活跃商品时,集合的size变了,active_products的cache_key更新;
  • 当某个活跃商品的updated_at变更时,集合的最大updated_at变了,cache_key也会更新;
  • 删除商品同理,集合size变化触发键更新。

如果你偏好手动删除缓存的方式(比如某些复杂场景下),也可以用回调触发删除:

class Product < ApplicationRecord
  def self.active_products_summary
    Rails.cache.fetch("active_products_summary") do
      where(status: :active).pluck(:name, :price)
    end
  end

  # 当商品创建、更新、删除时,清除集合缓存
  after_commit :clear_active_products_cache, on: [:create, :update, :destroy]

  private

  def clear_active_products_cache
    Rails.cache.delete("active_products_summary")
  end
end

不过这种方式需要维护回调,不如利用集合cache_key的方式省心。

3. 额外优化:加版本号应对逻辑变更

如果你的缓存计算逻辑或者返回的数据格式变了(比如把折扣从8折改成7折),可以给缓存键加一个版本号,这样不需要等对象变更就能让所有旧缓存失效:

def discounted_price
  Rails.cache.fetch([self, "discounted_price", "v2"]) do
    # 新的计算逻辑
    price * 0.7
  end
end

只要把版本号从v1改成v2,所有相关的旧缓存都会被自动抛弃,直接生成新缓存。


内容的提问来源于stack exchange,提问作者Daniel Viglione

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:36:32