如何防止初始化程序配置在开发模式中丢失?

时间:2022-09-11 00:13:35

I'm working on a Rails app that uses an engine. I'm using an initializer to configure one of my engine's controllers so that it will trigger an action in the host app. The code looks something like this:

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

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

This works fine in production, but in development mode, the proc is only called on the first request. This is because the classes are getting reloaded and this configuration is lost, because it was stored in a class variable. (For example, MyEngine::SomeController is reloaded from the file it's in, and since the after_filter isn't declared there, it isn't added back on.)

这在生产中工作正常,但在开发模式下,proc仅在第一个请求时调用。这是因为类被重新加载并且此配置丢失,因为它存储在类变量中。 (例如,MyEngine :: SomeController从它所在的文件重新加载,并且因为after_filter没有在那里声明,所以不会重新添加它。)

Some Rails background

In development mode, Rails uses the following load strategy:

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

  • Code in the app directory is reloaded on each request, on the assumption that you're actively changing it.
  • 应用程序目录中的代码会在每个请求上重新加载,前提是您正在主动更改它。
  • Code in the lib directory, along with config/initializer files, are loaded once, when the application boots.
  • 当应用程序启动时,lib目录中的代码以及config / initializer文件将被加载一次。

Initializer files are generally used for configuring gems. In the past, gems have mostly had code in the lib directory, so running their configuration once was sufficient.

初始化程序文件通常用于配置gem。在过去,gems主要在lib目录中有代码,因此运行一次配置就足够了。

How engines change things

However, Rails engines have code in the app directory: controllers, models, etc. These files are reloaded in development mode on each request. Therefore, configuration like my example above is lost.

但是,Rails引擎在app目录中有代码:控制器,模型等。这些文件在每个请求的开发模式下重新加载。因此,上面的示例配置将丢失。

Enter to_prepare

Rails provides config.to_prepare specifically to solve this problem: it run once in production, and on every request in development.

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

For example, we have this in application.rb, which works fine:

例如,我们在application.rb中有这个,它工作正常:

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

However, if I have to put all my engines' configuration in application.rb, this defeats the point of config/initializers in keeping things organized.

但是,如果我必须将所有引擎的配置放在application.rb中,这会使配置/初始化程序失败,以保持组织有序。

So, for any configuration of classes in my engines' app directories, I want to put that code in files under config/initializers.

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

Here are my questions.

这是我的问题。

  • I'm unclear how to get config into scope in an initializer file. I'm thinking it would be Rails.application.config. Is that right?
  • 我不清楚如何在初始化文件中将配置纳入范围。我在想它会是Rails.application.config。是对的吗?
  • Can I add add multiple to_prepare blocks? I'm afraid that calling it multiple times will overwrite previous blocks.
  • 我可以添加多个to_prepare块吗?我担心多次调用它会覆盖以前的块。

Update

As @Frederick Cheung mentioned, Rails.application.config.to_prepare does work in config/initializer files, and one can use as many of these as needed in the various files; each call appends its block to an array, so nothing is overwritten.

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

So the solution to this problem is:

所以这个问题的解决方案是:

# 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

One thing that still seems odd: I expected the to_prepare block to be called on every request in development mode, but instead it seems to be called randomly every 3rd request or so. I added block:

有一点似乎仍然很奇怪:我期望在开发模式下的每个请求上调用to_prepare块,但它似乎每隔3个请求随机调用一次。我添加了块:

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

... restarted my app, and refreshed the page nine times. I only saw the message on the 1st, 5th, 7th and 9th requests. I'm not sure what explains this behavior, but it does explain why my code without the to_prepare worked intermittently in development.

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

2 个解决方案

#1


10  

You can add as many to_prepare blocks as you want - when you do config.to_prepare, Rails is doing (in configuration.rb in railties)

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

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

and then iterates over those blocks handing them over to ActionDispatch::Reloader, where to_prepare is implemented using ActiveSupport::Callbacks (i.e. the same thing that is used for before_save and so on). Multiple to_prepare blocks are fine.

然后遍历这些块,将它们交给ActionDispatch :: Reloader,其中to_prepare是使用ActiveSupport :: Callbacks实现的(即与before_save相同的东西,依此类推)。多个to_prepare块都可以。

Currently it looks like Rails iterates over to_prepare_blocks after reading application initialisers so adding to Rails.application.configuration.to_prepare should work. You may prefer to use ActionDispatch::Reloader.to_prepare.

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

#2


1  

There's nothing to stop you from doing initializer code in a file that lives in app/models.

没有什么可以阻止你在app / models中的文件中执行初始化代码。

for example

例如

class MyClass
  def self.run_me_when_the_class_is_loaded
  end
end

MyClass.run_me_when_the_class_is_loaded

MyClass.run_me... will run when the class is loaded .... which is what we want, right?

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

Not sure if its the Rails way.... but its extremely straightforward, and does not depend on the shifting winds of Rails.

不确定它是否是Rails方式....但它非常简单,并且不依赖于Rails的转移风。

#1


10  

You can add as many to_prepare blocks as you want - when you do config.to_prepare, Rails is doing (in configuration.rb in railties)

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

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

and then iterates over those blocks handing them over to ActionDispatch::Reloader, where to_prepare is implemented using ActiveSupport::Callbacks (i.e. the same thing that is used for before_save and so on). Multiple to_prepare blocks are fine.

然后遍历这些块,将它们交给ActionDispatch :: Reloader,其中to_prepare是使用ActiveSupport :: Callbacks实现的(即与before_save相同的东西,依此类推)。多个to_prepare块都可以。

Currently it looks like Rails iterates over to_prepare_blocks after reading application initialisers so adding to Rails.application.configuration.to_prepare should work. You may prefer to use ActionDispatch::Reloader.to_prepare.

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

#2


1  

There's nothing to stop you from doing initializer code in a file that lives in app/models.

没有什么可以阻止你在app / models中的文件中执行初始化代码。

for example

例如

class MyClass
  def self.run_me_when_the_class_is_loaded
  end
end

MyClass.run_me_when_the_class_is_loaded

MyClass.run_me... will run when the class is loaded .... which is what we want, right?

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

Not sure if its the Rails way.... but its extremely straightforward, and does not depend on the shifting winds of Rails.

不确定它是否是Rails方式....但它非常简单,并且不依赖于Rails的转移风。