Ruby 中的自动加载路径和嵌套服务类崩溃 [英] Autoload paths and nested services classes crash in Ruby

查看:42
本文介绍了Ruby 中的自动加载路径和嵌套服务类崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've multiple issues to load / require classes under my app/services folder in a Rails 5 project and I'm starting to give up on this issue.

首先要明确的是,services/ 是我在整个项目中使用的简单 PORO 类,用于从控制器、模型等中抽象出大部分业务逻辑.

First of all and to be clear, services/ are simple PORO classes I use throughout my project to abstract most of the business logic from the controllers, models, etc.

树看起来像这样

app/
 services/
  my_service/
    base.rb
    funny_name.rb
  my_service.rb  
models/
 funny_name.rb

失败 #1

首先,当我尝试使用 MyService.const_get('FunnyName') 时,它从我的模型目录中获取了 FunnyName.但是,当我直接执行 MyService::FunnyName 时,它似乎没有相同的行为,但在我的大多数测试和更改中,这都运行良好,这很奇怪.

Failure #1

First, when I tried to use MyService.const_get('FunnyName') it got FunnyName from my models directory. It does not seem to have the same behavior when I do MyService::FunnyName directly though, in most of my tests and changes this was working fine, it's odd.

我意识到 Rails config.autoload_paths 不会递归加载;第一个被捕获的 FunnyNamemodels/funny_name.rb 是有道理的,因为它肯定是加载的,但不是另一个.

I realised Rails config.autoload_paths does not load things recursively ; it would makes sense that the first FunnyName to be catch is the models/funny_name.rb because it's definitely loaded but not the other.

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

That's ok, let's find a workaround. I added this to my application.rb :

config.autoload_paths += Dir[Rails.root.join('app', 'services', '**/')]

这会将服务的所有子目录添加到 config.autoload_paths 中.从 Rails 5 开始,显然不建议写这样的东西;但这个想法在我看来是正确的.

Which will add all the subdirectories of services into config.autoload_paths. Apparently it's not recommended to write things like that since Rails 5 ; but the idea does look right to me.

现在,当我启动我的应用程序时,它崩溃并输出类似这样的内容

Now, when I start my application it crashes and output something like this

无法自动加载常量 Base,预期/.../backend/app/services/my_service/base.rb 来定义它(LoadError)

Unable to autoload constant Base, expected /.../backend/app/services/my_service/base.rb to define it (LoadError)

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

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

The thing is, base.rb is defined in the exact file the error leads me, which contains something like

class MyService
  class Base
  end
end

糟糕的解决方案

所以我尝试了其他解决方法,很多方法,但没有任何效果.所以我最终完全删除了 autoload_paths 并直接添加到 application.rb

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

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

Now the base.rb is correctly loaded, the MyService.const_get('FunnyName') will actually return the correct class and everything works, but it's a disgusting workaround. Also, it has yet not been tested in production but it might create problems depending the environment.

application.rb 中获取整个树听起来是个坏主意,我认为不能保持这种方式.

Requiring the whole tree from the application.rb sounds like a bad idea and I don't think it can be kept this way.

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

What's the cleanest way to add custom services/ directory in Rails ? It contains multiple subdirectories and classes with simple names which are also present in other parts of the app (models, base.rb, etc.)

如何避免混淆 autoload_paths ?还有什么我不知道的东西可以解决问题吗?为什么 base.rb 甚至会在这里崩溃?

How do you avoid confusing the autoload_paths ? Is there something else I don't know which could do the trick ? Why did base.rb even crash here ?

推荐答案

工作解决方案

经过更深入的调查和尝试后,我意识到我必须eager_load 服务以避免在调用元功能(例如 const_get('MyClassWithModelName'))时出现错误的常量.

Working solution

After deeper investigation and attempts, I realised that I had to eager_load the services to avoid getting wrong constants when calling meta functionalities such as const_get('MyClassWithModelName').

但问题是:经典的 eager_load_paths 将不起作用,因为出于某种原因,这些类显然会在 Rails 的整个核心初始化之前加载,而简单的类名如 Base 实际上会与核心混淆,从而导致一切崩溃.

But here's is the thing : the classic eager_load_paths won't work because for some reason those classes will apparently be loaded before the entire core of Rails is initialized, and simple class names such as Base will actually be mixed up with the core, therefore make everything crash.

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

Some could say "then rename Base into something else" but should I change a class name wrapped into a namespace because Rails tell me to ? I don't think so. Class names should be kept simple, and what I do inside a custom namespace is no concern of Rails.

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

I had to think it through and write down my own hook of Rails configuration. We load the core and all its functionalities and then service/ recursively.

顺便说一句,它不会给生产环境增加任何重量,而且非常方便开发.

On a side note, it won't add any weight to the production environment, and it's very convenient for development.

把它放在 config/environment/development.rb 和所有其他你想要在没有 Rails 类冲突的情况下快速加载的环境(例如 test.rb 在我的例子中)

Place this in config/environment/development.rb and all other environment you want to eager load without Rails class conflicts (such as test.rb in my case)

# 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', '**/')]

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

Then create a new file initializers/after_eager_load_paths.rb containing this

# 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

就像一个魅力.如果需要,您还可以通过 load 更改 require.

Works like a charm. You can also change require by load if you need it.

这篇关于Ruby 中的自动加载路径和嵌套服务类崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆