我在Rails 5项目的app/services
文件夹下需要加载/需要类的问题很多,而我开始放弃这个问题。
首先要明确的是,services/
是我在整个项目中使用的简单PORO类,用于从控制器,模型等中抽象出大多数业务逻辑。
树看起来像这样
app/
services/
my_service/
base.rb
funny_name.rb
my_service.rb
models/
funny_name.rb
首先,当我尝试使用MyService.const_get('FunnyName')
时,它从我的模型目录中获得了FunnyName
。但是,当我直接进行MyService::FunnyName
时,它似乎没有相同的行为,但是在我的大多数测试和更改中,这都可以正常工作,这很奇怪。
我意识到Rails config.autoload_paths
不会递归加载;可以理解的是,第一个FunnyName
是models/funny_name.rb
,因为它肯定是已加载的,而不是其他。
没关系,让我们找到一种解决方法。我将此添加到了application.rb
中:
config.autoload_paths += Dir[Rails.root.join('app', 'services', '**/')]
这会将服务的所有子目录添加到config.autoload_paths
中。显然,从Rails 5开始,不建议编写类似的内容。但是这个主意确实适合我。
现在,当我启动我的应用程序时,它崩溃并输出如下内容
无法自动加载常量Base,应为 /.../backend/app/services/my_service/base.rb进行定义(LoadError)
名称已更改,但这是我之前写的树的匹配路径
问题是,base.rb
是在错误导致我进入的确切文件中定义的,其中包含类似
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
中进行过测试,但根据环境的不同,可能会产生问题。
从application.rb
中请求整棵树听起来是个坏主意,我认为不能这样保存。
在Rails中添加自定义services/
目录的最干净方法是什么?它包含多个具有简单名称的子目录和类,这些名称也存在于应用程序的其他部分(模型,base.rb
等)
如何避免混淆autoload_paths
?还有其他我不知道的方法可以解决这个问题吗?为什么base.rb
甚至在这里崩溃?
答案 0 :(得分:3)
经过更深入的研究和尝试,我意识到我必须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', '**/')]
然后创建一个包含该文件的新文件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
像魅力一样工作。如果需要,您还可以通过require
来更改load
。
答案 1 :(得分:1)
当我这样做(在我所有的项目中)时,它看起来像这样:
$(document).ready(function() {
$("table #libelleArticle").change(function() {
// le problem how to send a price param from thymeleaf to jquery
$("#prix").val(" "); // price input
});
});
我将所有常见的方法定义放在app
|- services
| |- sub_service
| | |- service_base.rb
| | |- useful_service.rb
| |- service_base.rb
中:
app / services / service_base.rb
app/services/service_base.rb
我在class ServiceBase
attr_accessor *%w(
args
).freeze
class < self
def call(args={})
new(args).call
end
end
def initialize(args)
@args = args
end
end
中放置了sub_services
通用的所有方法:
app / services / sub_service / service_base.rb
app/services/sub_service/service_base.rb
然后是class SubService::ServiceBase < ServiceBase
def call
end
private
def a_subservice_method
end
end
中的所有唯一方法:
app / services / sub_service / useful_service.rb
useful_service
然后,我可以做类似的事情:
class SubService::UsefulService < SubService::ServiceBase
def call
a_subservice_method
a_useful_service_method
end
private
def a_useful_service_method
end
end
答案 2 :(得分:0)
带着你的树
app/
services/
my_class/
base.rb
funny_name.rb
my_class.rb
models/
funny_name.rb
services / my_class / base.rb应该类似于:
module MyClass
class Base
services / my_class / funny_name.rb应该类似于:
module MyClass
class FunnyName
services / my_class.rb应该类似于:
class MyClass
models / funny_name.rb应该类似于:
class FunnyName
我说“应该看起来像”,因为类/模块是可互换的; Rails只是想在这些位置定义这些常量。
您无需在自动加载路径中添加任何内容。 Rails会自动提取app
轶事:在您的服务目录中,将它们的命名约定(文件名和基础常量)视为“ _service.rb”或“ ThingService”是很常见的,就像控制器的外观一样。模型没有这个后缀,因为它们被视为一流的对象。
GitLab具有一些很棒的文件结构,非常值得一看。 https://gitlab.com/gitlab-org/gitlab-ce