如何在Rails的开发模式下自动为每个请求重新加载gem代码?

时间:2014-03-17 18:57:58

标签: ruby-on-rails activesupport

我正在开发一个Rails应用程序,其中大部分不是特定应用程序的代码都是在各种宝石中编写的,包括一些Rails引擎和一些我正在增强或修复错误的第三方宝石。

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

由于这些宝石中的很多代码实际上都是应用程序的一部分,因此它仍然经常发生变化。我希望能够利用Rails功能,其中在开发时(即config.cache_classes为假时)在每个请求上重新加载代码,但这仅在默认情况下在正常应用程序结构内完成。

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

2 个答案:

答案 0 :(得分:3)

ActiveSupport的帮助下,我通过反复试验找到了需要采取的几个步骤。

  • activesupport个文件

    中添加.gemspec作为依赖项
    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)自动加载的综合帖子。

答案 1 :(得分:0)

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

  • 使用 path: 中的 Gemfile 选项将 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