如何扩展“不可加载”Rails插件?

时间:2021-07-02 00:14:06

I'm trying to write a plugin that will extend InheritedResources.

我正在编写一个插件来扩展继承和继承。

Specifically I want to rewrite some default helpers.

我特别想重写一些默认的helper函数。

And I'd like it to "just work" once installed, w/o any changes to application code.

而且我希望它一旦安装就能“正常工作”,不需要对应用程序代码做任何修改。

The functionality is provided in a module which needs to be included in a right place. The question is where? :)

这个功能是在一个需要被包含在一个正确的地方的模块中提供的。问题是在哪里?:)

The first attempt was to do it in my plugin's init.rb:

第一次尝试是在我的插件init.rb:

InheritedResources::Base.send :include, MyModule

It works in production, but fails miserably in development since InheritedResource::Base declared as unloadable and so its code is reloaded on each request. So my module is there for the first request, and then its gone.

它可以在生产环境中工作,但在开发过程中严重失败,因为InheritedResource::Base声明为不可加载的,因此它的代码会在每个请求上重新加载。我的模块在第一个请求中,然后它就消失了。

InheritedResource::Base is 'pulled' in again by any controller that uses it:

:Base会被使用它的任何控制器再次“拉入”:

Class SomeController < InheritedResource::Base

But no code is 'pulling in' my extension module since it is not referenced anywhere except init.rb which is not re-loaded on each request

但是没有代码是“拉入”我的扩展模块,因为除了init外,它在任何地方都没有引用。没有在每个请求上重新加载的rb

So right now I'm just including the module manually in every controller that needs it which sucks. I can't even include it once in ApplicationController because InheritedResources inherites from it and so it will override any changes back.

现在我只是在每个需要它的控制器中手工地包含这个模块这很糟糕。我甚至不能在ApplicationController中包含它一次,因为继承edresources会继承它,所以它会覆盖所有的更改。

update

I'm not looking for advice on how to 'monkey patch'. The extension is working in production just great. my problem is how to catch moment exactly after InheritedResources loaded to stick my extension into it :)

我不是在寻找关于如何“猴子补丁”的建议。这个扩展在生产中非常有用。我的问题是,在继承了我的扩展之后,如何准确地抓住时机:)

update2

another attempt at clarification:

另一个尝试澄清:

the sequence of events is

事件的顺序是

  • a) rails loads plugins. my plugin loads after inherited_resources and patches it.
  • rails加载插件。我的插件在继承了_resources并对其进行了补丁后加载。
  • b) a development mode request is served and works
  • b)服务和工作的开发模式请求。
  • c) rails unloads all the 'unloadable' code which includes all application code and also inherited_resources
  • c) rails卸载所有“不可加载”代码,其中包括所有应用程序代码,也继承了ed_resources
  • d) another request comes in
  • d)另一个请求进来了
  • e) rails loads controller, which inherites from inherited resources
  • e) rails加载控制器,该控制器继承自继承的资源
  • f) rails loads inherited resources which inherit from application_controller
  • f) rails加载继承自application_controller的继承资源
  • g) rails loads application_contrller (or may be its already loaded at this stage, not sure)
  • g) rails加载application_contrller(或者在这个阶段已经加载了,不确定)
  • g) request fails as no-one loaded my plugin to patch inherited_resources. plugin init.rb files are not reloaded
  • g)请求失败,因为没有人加载我的插件来修补inherited_resources。插件初始化。不会重新加载rb文件

I need to catch the point in time between g and h

我需要在g和h之间找到时间点。

2 个解决方案

#1


4  

The Rails::Configuration, config in the environment files, allows registering a callback on the dispatcher that runs before each request in development mode, or once when in production mode.

Rails::Configuration是环境文件中的配置,允许在dispatcher上注册一个回调函数,在开发模式下运行在每个请求之前,或者在生产模式下运行一次。

config.to_prepare do
   # do something here
end

The problem is, I don't think your plugin has access to config when the init.rb file is run. Here is a way to register your callback directly in the dispatcher. Just put this in the init.rb file.

问题是,我不认为你的插件可以在初始化时访问配置。rb文件运行。这里有一种方法可以直接在分派器中注册回调函数。把这个放到init中。rb文件。

require 'dispatcher'
::Dispatcher.to_prepare do
    puts "hi there from a plugin"
end

Warning: I don't know what side effects this may have. If possible, try to get access to config and register the callback tha right way.

警告:我不知道这可能有什么副作用。如果可能的话,尝试访问配置并以正确的方式注册回调。

#2


-1  

What you are attempting to do is usually called "MonkeyPatch" - changing the way one module or class is working by "overriding" methods.

你想要做的事情通常被称为“MonkeyPatch”——改变一个模块或类的工作方式是“覆盖”方法。

It is a common practice in Rails, but it doesn't mean it is the best way to do things - when possible, it is better to use common inheritance (it is more explicit about the changes you make).

这在Rails中是一种常见的实践,但并不意味着这是最好的方法——如果可能的话,最好使用公共继承(您所做的更改更明确)。

Regarding your questions about "where to put the files": it is usually the lib/ directory. This can mean the lib of the rails app, or a lib directory inside a gem or plugin, if you are into that sort of thing.

关于“文件放在哪里”的问题:通常是lib/目录。这可以是rails应用程序的lib,或者gem或插件中的lib目录,如果您喜欢这类东西的话。

For example, if the file you want to change is lib/generators/rails/templates/controller.rb of inherited resources, the first thing you have to do is replicate that directory structure inside your lib/ folder ('lib/generators/rails/templates/controller.rb')

例如,如果要更改的文件是lib/generator /rails/template /controller。对于继承的资源,首先要做的是在lib/文件夹中复制目录结构('lib/generator /rails/templates/controller.rb')

Inside that new file of yours, (empty at the beginning) you can override methods. However, you must also the modules/classes hierarchy. So if the original gem had this:

在您的新文件中(开始为空)可以覆盖方法。但是,您还必须使用模块/类层次结构。所以如果最初的宝石有:

module foo
  module bar
    def f1
    ...
    end
    def f2
    ...
    end
  end
  def f3
  ...
  end
end

And you wanted to modify f1, you would have to respect the foo-bar modules.

你想要修改f1,你必须尊重foo-bar模块。

module foo
  module bar
    def f1
    ... # your code here
    end
  end
end

Now the last thing you need is to make sure this code is executed at the right time. If you are using the application's lib/ folder, you will need to create an entry on the initializers/ folder and require your new file. If you are developing a gem/plugin, you will have a init.rb file on the "root" folder of that plugin. Put the 'require' there.

现在,您最不需要的就是确保在正确的时间执行此代码。如果您正在使用应用程序的库/文件夹,您将需要在初始化器/文件夹上创建一个条目,并需要您的新文件。如果您正在开发gem/plugin,那么您将拥有一个init。在那个插件的“根”文件夹上的rb文件。把“需要”。

I'm not very familiar with this unloadable stuff; maybe I'm asking something obvious but- have you tried making your extension module unloadable, too? (You shouldn't need this if you monkeypatched the module instead of creating a new one)

我不太熟悉这个不可装载的东西;也许我在问一些显而易见的问题,但是——你也尝试过让你的扩展模块不可加载吗?(如果您对模块进行了修改,而不是创建一个新的模块,那么就不需要这个)

#1


4  

The Rails::Configuration, config in the environment files, allows registering a callback on the dispatcher that runs before each request in development mode, or once when in production mode.

Rails::Configuration是环境文件中的配置,允许在dispatcher上注册一个回调函数,在开发模式下运行在每个请求之前,或者在生产模式下运行一次。

config.to_prepare do
   # do something here
end

The problem is, I don't think your plugin has access to config when the init.rb file is run. Here is a way to register your callback directly in the dispatcher. Just put this in the init.rb file.

问题是,我不认为你的插件可以在初始化时访问配置。rb文件运行。这里有一种方法可以直接在分派器中注册回调函数。把这个放到init中。rb文件。

require 'dispatcher'
::Dispatcher.to_prepare do
    puts "hi there from a plugin"
end

Warning: I don't know what side effects this may have. If possible, try to get access to config and register the callback tha right way.

警告:我不知道这可能有什么副作用。如果可能的话,尝试访问配置并以正确的方式注册回调。

#2


-1  

What you are attempting to do is usually called "MonkeyPatch" - changing the way one module or class is working by "overriding" methods.

你想要做的事情通常被称为“MonkeyPatch”——改变一个模块或类的工作方式是“覆盖”方法。

It is a common practice in Rails, but it doesn't mean it is the best way to do things - when possible, it is better to use common inheritance (it is more explicit about the changes you make).

这在Rails中是一种常见的实践,但并不意味着这是最好的方法——如果可能的话,最好使用公共继承(您所做的更改更明确)。

Regarding your questions about "where to put the files": it is usually the lib/ directory. This can mean the lib of the rails app, or a lib directory inside a gem or plugin, if you are into that sort of thing.

关于“文件放在哪里”的问题:通常是lib/目录。这可以是rails应用程序的lib,或者gem或插件中的lib目录,如果您喜欢这类东西的话。

For example, if the file you want to change is lib/generators/rails/templates/controller.rb of inherited resources, the first thing you have to do is replicate that directory structure inside your lib/ folder ('lib/generators/rails/templates/controller.rb')

例如,如果要更改的文件是lib/generator /rails/template /controller。对于继承的资源,首先要做的是在lib/文件夹中复制目录结构('lib/generator /rails/templates/controller.rb')

Inside that new file of yours, (empty at the beginning) you can override methods. However, you must also the modules/classes hierarchy. So if the original gem had this:

在您的新文件中(开始为空)可以覆盖方法。但是,您还必须使用模块/类层次结构。所以如果最初的宝石有:

module foo
  module bar
    def f1
    ...
    end
    def f2
    ...
    end
  end
  def f3
  ...
  end
end

And you wanted to modify f1, you would have to respect the foo-bar modules.

你想要修改f1,你必须尊重foo-bar模块。

module foo
  module bar
    def f1
    ... # your code here
    end
  end
end

Now the last thing you need is to make sure this code is executed at the right time. If you are using the application's lib/ folder, you will need to create an entry on the initializers/ folder and require your new file. If you are developing a gem/plugin, you will have a init.rb file on the "root" folder of that plugin. Put the 'require' there.

现在,您最不需要的就是确保在正确的时间执行此代码。如果您正在使用应用程序的库/文件夹,您将需要在初始化器/文件夹上创建一个条目,并需要您的新文件。如果您正在开发gem/plugin,那么您将拥有一个init。在那个插件的“根”文件夹上的rb文件。把“需要”。

I'm not very familiar with this unloadable stuff; maybe I'm asking something obvious but- have you tried making your extension module unloadable, too? (You shouldn't need this if you monkeypatched the module instead of creating a new one)

我不太熟悉这个不可装载的东西;也许我在问一些显而易见的问题,但是——你也尝试过让你的扩展模块不可加载吗?(如果您对模块进行了修改,而不是创建一个新的模块,那么就不需要这个)