如何在开发模式中避免初始化器配置丢失



我正在开发一个使用引擎的Rails应用程序。我正在使用一个初始化器来配置我的引擎的一个控制器,这样它就会在主机应用程序中触发一个操作。代码看起来像这样:

# config/initializers/my_engine.rb
MyEngine::SomeController.after_filter proc {
  # Do something in the host app
}, :only => :update

这在生产中运行良好,但在开发模式中,只有在第一个请求时才会调用proc。这是因为类正在重新加载,而此配置已丢失,因为它存储在类变量中。(例如,MyEngine::SomeController是从它所在的文件中重新加载的,由于after_filter没有在那里声明,所以它不会重新添加到上。)

一些Rails背景

在开发模式中,Rails使用以下加载策略:

  • app目录中的代码会在每次请求时重新加载,前提是您正在主动更改它
  • 当应用程序启动时,lib目录中的代码和config/initializer文件将被加载一次

初始化程序文件通常用于配置gem。在过去,gem的代码大多位于lib目录中,因此只运行一次配置就足够了。

发动机如何改变事物

然而,Rails引擎在app目录中有代码:控制器、模型等。这些文件在每次请求时都会在开发模式下重新加载。因此,像我上面的例子这样的配置就丢失了。

输入准备(_P)

Rails专门提供了config.to_prepare来解决这个问题:它在生产中运行一次,在开发中的每个请求上都运行一次。

例如,我们在application.rb中有这个,它运行良好:

config.to_prepare do
  # set up class variables (after_filters, etc)
end

然而,如果我必须将所有引擎的配置都放在application.rb中,这就违背了config/initializers保持事物有序性的要点。

因此,对于引擎的app目录中的任何类配置,我都希望将代码放在config/initializers下的文件中。

以下是我的问题。

  • 我不清楚如何将config放入初始值设定项文件中的作用域。我想应该是Rails.application.config。是这样吗
  • 我可以添加多个to_prepare块吗?我担心多次调用它会覆盖以前的块

更新

正如@Frederick Cheung所提到的,Rails.application.config.to_prepare确实在config/initializer文件中工作,并且可以根据需要在各种文件中使用任意多个;每个调用都将其块附加到一个数组中,因此不会覆盖任何内容。

因此这个问题的解决方案是:

# config/initializers/my_engine.rb
Rails.application.config.to_prepare do
  MyEngine::SomeController.after_filter proc {
    # Do something in the host app
  }, :only => :update
end

有一件事似乎仍然很奇怪:在开发模式下,我希望to_prepare块在每个请求上都被调用,但它似乎每隔三个请求左右就会被随机调用。我添加了块:

Rails.application.config.to_prepare do
  Rails.logger.info "Running the prepare block!"
end

重新启动我的应用程序,并刷新页面九次。我只在第1次、第5次、第7次和第9次请求时看到了这条消息。我不确定是什么解释了这种行为,但它确实解释了为什么我的代码没有to_prepare在开发中间歇性地工作。

您可以添加任意数量的to_prepare块-当您执行config.to_prepare时,Rails正在执行(在railties中的configuration.rb中)

def to_prepare(&blk)
  to_prepare_blocks << blk if blk
end

然后在将它们移交给ActionDispatch::Reloader的那些块上迭代,其中to_prepare是使用ActiveSupport::Callbacks实现的(即与before_save等使用的相同)。多个to_prepare块是可以的。

目前,Rails在读取应用程序初始化程序后似乎会在to_prepare_blocks上进行迭代,因此添加到Rails.application.configuration.to_prepare应该可以工作。您可能更喜欢使用ActionDispatch::Reloader.to_prepare

没有什么可以阻止您在应用程序/模型中的文件中执行初始化器代码。

例如

class MyClass
  def self.run_me_when_the_class_is_loaded
  end
end
MyClass.run_me_when_the_class_is_loaded

MyClass.run_me…将在加载类时运行。。。。这就是我们想要的,对吧?

不确定是否是Rails方式。。。。但它非常简单,并不依赖于Rails的风向变化。

最新更新