自动加载路径和嵌套服务类在Ruby中崩溃

Lau*_*ent 6 ruby namespaces ruby-on-rails ruby-on-rails-5

app/services在Rails 5项目中的文件夹下需要加载/需要类的问题很多,而我开始放弃这个问题。

首先要弄清楚的是,services/我在整个项目中都使用了简单的PORO类,以从控制器,模型等中抽象出大多数业务逻辑。

这棵树看起来像这样

app/
 services/
  my_service/
    base.rb
    funny_name.rb
  my_service.rb  
models/
 funny_name.rb
Run Code Online (Sandbox Code Playgroud)

失败#1

首先,当我尝试使用MyService.const_get('FunnyName')它时,它FunnyName来自我的models目录。MyService::FunnyName但是,当我直接进行操作时,它似乎没有相同的行为,在我的大多数测试和更改中,此方法都可以正常工作,这很奇怪。

我意识到Rails config.autoload_paths不会递归地加载东西;有意义的是,第一个FunnyName被捕获的是,models/funny_name.rb因为它肯定是已加载的,而不是另一个。

没关系,让我们找到一种解决方法。我将此添加到我的application.rb

config.autoload_paths += Dir[Rails.root.join('app', 'services', '**/')]
Run Code Online (Sandbox Code Playgroud)

这会将服务的所有子目录添加到中config.autoload_paths。显然,从Rails 5开始,不建议编写类似的内容。但是这个想法对我来说确实不错。

失败#2

现在,当我启动我的应用程序时,它崩溃并输出如下内容

无法自动加载常量Base,需要/.../backend/app/services/my_service/base.rb进行定义(LoadError)

名称已更改,但这是我之前编写的树的匹配路径

问题是,base.rb在错误导致我进入的确切文件中进行了定义,其中包含类似

class MyService
  class Base
  end
end
Run Code Online (Sandbox Code Playgroud)

解决方案差

因此,我尝试了其他解决方法,其中很多方法都无效。因此,我最终完全删除了autoload_paths,然后直接将其添加到application.rb

Dir[Rails.root.join('app', 'services', '**', '*.rb')].each { |file| require file }
Run Code Online (Sandbox Code Playgroud)

现在base.rb已正确加载,MyService.const_get('FunnyName')实际上将返回正确的类,并且一切正常,但这是一种令人作呕的解决方法。此外,它尚未经过测试,production但根据环境可能会产生问题。

application.rb声音中要求整棵树是个坏主意,我不认为可以这样保留。

services/在Rails中添加自定义目录的最干净方法是什么?它包含多个具有简单名称的子目录和类,这些名称也存在于应用程序的其他部分(模型base.rb等)中。

您如何避免混淆autoload_paths?还有其他我不知道的方法可以解决这个问题吗?为什么在base.rb这里甚至崩溃?

Lau*_*ent 5

工作方案

经过更深入的研究和尝试,我意识到我不得不eager_load使用服务以避免在调用诸如的元功能时出现错误的常量const_get('MyClassWithModelName')

事情是这样的:经典之作eager_load_paths无法正常工作,因为出于某种原因,这些类显然会在初始化Rails的整个核心之前就被加载,而简单的类名(例如)Base实际上会与核心混在一起,因此会使所有崩溃。

有人可能会说“然后将Base重命名为其他名称”,但是由于Rails告诉我,我应该更改包装到名称空间中的类名吗?我不这么认为。类名应该保持简单,我在自定义名称空间中所做的事情与Rails无关。

我必须仔细考虑并写下我自己的Rails配置钩子。我们加载核心及其所有功能,然后service/递归地加载。

附带说明,它不会给生产环境增加任何负担,并且非常便于开发。

要添加的代码

将其放置在config/environment/development.rb您希望加载的所有其他环境中,而不会出现Rails类冲突(例如test.rb我的情况)

# we eager load all services and subdirectories after Rails itself has been initializer
# why not use `eager_load_paths` or `autoload_paths` ? it makes conflict with the Rails core classes
# here we do eager them the same way but afterwards so it never crashes or has conflicts.
# see `initializers/after_eager_load_paths.rb` for more details
config.after_eager_load_paths = Dir[Rails.root.join('app', 'services', '**/')]
Run Code Online (Sandbox Code Playgroud)

然后创建一个initializers/after_eager_load_paths.rb包含以下内容的新文件

# this is a customized eager load system
# after Rails has been initialized and if the `after_eager_load_paths` contains something
# we will go through the directories recursively and eager load all ruby files
# this is to avoid constant mismatch on startup with `autoload_paths` or `eager_load_paths`
# it also prevent any autoload failure dû to deep recursive folders with subclasses
# which have similar name to top level constants.
Rails.application.configure do
  if config.respond_to?(:after_eager_load_paths) && config.after_eager_load_paths.instance_of?(Array)
    config.after_initialize do
      config.after_eager_load_paths.each do |path|
        Dir["#{path}/*.rb"].each { |file| require file }
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

奇迹般有效。您也可以require根据load需要进行更改。