【问题标题】:How can I automatically reload gem code on each request in development mode in Rails?如何在 Rails 的开发模式下自动重新加载每个请求的 gem 代码?
【发布时间】:2014-03-17 18:57:58
【问题描述】:

我正在开发一个 Rails 应用程序,其中大部分不特定于应用程序的代码已编写在各种 gem 中,包括一些 Rails 引擎和一些我正在增强或修复错误的 3rd 方 gem。

gem 'mygem', path: File.expath_path('../../mygem', __FILE__)

由于这些 gem 中的许多代码确实是应用程序的一部分,因此它仍然经常更改。我希望能够利用 Rails 功能,在开发时(即当 config.cache_classes 为 false 时)在每个请求上重新加载代码,但这仅在默认情况下在正常的应用程序结构中完成。

如何配置 Rails 以在每个请求上重新加载 gem 代码,就像使用应用程序代码一样?

【问题讨论】:

    标签: ruby-on-rails activesupport


    【解决方案1】:

    ActiveSupport的帮助下,我通过反复试验发现需要几个步骤。

    • .gemspec 文件中添加 activesupport 作为依赖项

      spec.add_dependency 'activesupport'
      
    • 在您的 gem 的顶级模块中包含 ActiveSupport::Dependencies(这是最难以捉摸的要求)

      require 'bundler'; Bundler.setup
      require 'active_support/dependencies'
      
      module MyGem
        unloadable
        include ActiveSupport::Dependencies
      end
      
      require 'my_gem/version.rb'
      # etc...
      
    • 设置您的 gem 以使用自动加载。您可以手动使用 ruby​​ autoload 声明将符号映射到文件名,或者使用 Rails 样式的文件夹结构到模块层次结构规则(参见 ActiveSupport #constantize

    • 在 gem 的每个模块和类中,添加 unloadable

      module MyModule
        unloadable
      end
      
    • 在依赖于 gem 中的模块或类的每个文件中,包括 gem 本身,在每个文件的顶部使用 require_dependency 声明它们。根据需要查找 gem 的路径以正确解析路径。

      require_dependency "#{Gem.loaded_specs['my_gem'].full_gem_path}/lib/my_gem/myclass"
      

    如果您在修改文件并发出请求后遇到异常,请检查您是否错过了依赖项。

    有关一些有趣的细节,请参阅 this 关于 Rails(和 ruby​​)自动加载的综合帖子。

    【讨论】:

    • 这个答案很棒,但现在有点过时了。另一个答案更适合今天。
    【解决方案2】:

    我用于 Rails 6 的解决方案,带有专用的 Zeitwerk 类加载器和文件检查器:

    • 使用Gemfile 中的path: 选项将gem 添加到Rails 项目中

      gem 'mygem', path: 'TODO'  # The root directory of the local gem
      
    • development.rb中,设置类加载器和文件观察器

      gem_path = 'TODO'  # The root directory of the local gem, the same used in Gemfile
      
      # Create a Zeitwerk class loader for each gem
      gem_lib_path = gem_path.join('lib').join(gem_path.basename)
      gem_loader = Zeitwerk::Registry.loader_for_gem(gem_lib_path)
      gem_loader.enable_reloading
      gem_loader.setup
      
      # Create a file watcher that will reload the gem classes when a file changes
      file_watcher = ActiveSupport::FileUpdateChecker.new(gem_path.glob('**/*')) do
        gem_loader.reload
      end
      
      # Plug it to Rails to be executed on each request
      Rails.application.reloaders << Class.new do 
        def initialize(file_watcher)
          @file_watcher = file_watcher
        end
      
        def updated?
          @file_watcher.execute_if_updated
        end
      end.new(file_watcher)
      

    这样,对于每个请求,如果其中一个已被修改,类加载器将重新加载 gem 类。

    详细攻略请看我的文章Embed a gem in a Rails project and enable autoreload

    【讨论】:

    • 这是一个很好的答案,链接的演练很棒。
    猜你喜欢
    • 2011-08-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 2011-02-23
    • 2012-02-05
    • 2011-04-30
    • 2013-04-18
    相关资源
    最近更新 更多