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

Zeitwerk环境下如何缓存类列表?含目录预加载问题

Rails开发环境下Zeitwerk的类列表缓存与目录预加载方案

问题描述

我正尝试缓存一份类列表,当前实现代码如下:

def models
  return @models if @models

  Zeitwerk::Loader.eager_load_all # 开发环境下需预加载所有模型,否则部分模型未加载会不在列表中
  @models = ActiveRecord::Base.descendants.select { |model| model.include?(SomeModule) }
end

但Zeitwerk在开发环境会随时重载类,导致@models缓存失效。另外还想知道如何程序化预加载单个目录,而非整个应用。

补充说明:该类列表仅用于特定rake任务和测试中,不用于生产运行代码。

附相关实现代码示例:

class ModelFoo
  annotated
  ...
end
class ModelBar
  annotated
  ...
end
module Annotating
  extend ActiveSupport::Concern

  module Model
    extend ActiveSupport::Concern

    included do
      has_many :annotations, as: :annotated, dependent: :destroy, autosave: true
    end

    ...
  end
  class_methods do
    def annotated
      class_eval do
        include Model
      end
    end
  end

  class << self
    def models
      <see above the body of this method>
    end
  end
end
ActiveRecord::Base.include(Annotating::Model)

后续会使用Annotating.models为所有标注模型生成触发器,并测试这些模型的标准操作。

解决方案

1. 解决开发环境缓存失效问题

由于你的类列表仅用于rake任务和测试场景,可通过以下两种方式处理缓存失效问题:

方案A:临时禁用Zeitwerk自动重载

在执行特定任务时关闭Zeitwerk的自动重载,确保加载后的类不会被重置,缓存即可保持有效:

class << self
  def models
    return @models if @models

    # 开发环境下临时关闭自动重载
    if Rails.env.development?
      Rails.application.config.zeitwerk.checks = false
      Rails.application.config.zeitwerk.enable_reloading = false
    end

    Zeitwerk::Loader.eager_load_all
    @models = ActiveRecord::Base.descendants.select { |model| model.include?(Annotating::Model) }
  end
end

该操作仅作用于当前任务流程,不会影响开发环境的日常代码重载功能。

方案B:移除缓存逻辑,每次调用重新生成

若Annotating.models调用频率较低,直接去掉缓存,每次调用都重新加载筛选:

class << self
  def models
    Zeitwerk::Loader.eager_load_all
    ActiveRecord::Base.descendants.select { |model| model.include?(Annotating::Model) }
  end
end

rake任务和测试场景下性能影响可忽略,还能彻底避免缓存失效问题。

2. 程序化预加载单个目录

Zeitwerk的每个loader对应一个目录,可定位目标目录的loader并单独预加载,无需全局加载整个应用:

比如要预加载app/models/annotated目录:

class << self
  def models
    return @models if @models

    preload_target_directory
    @models = ActiveRecord::Base.descendants.select { |model| model.include?(Annotating::Model) }
  end

  private

  def preload_target_directory
    target_dir = Rails.root.join("app/models/annotated").to_s
    loader = Zeitwerk::Loader.loaders.find { |l| l.dirs.include?(target_dir) }
    loader&.eager_load
  end
end

如果所有标注模型都集中在同一目录,这种方式能精准加载所需内容,提升加载效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:26:09