Feature #35415


Unified plugin api esp. regarding patches

Added by Ludwig Meysel about 3 years ago. Updated almost 2 years ago.

Plugin API
Target version:
Start date:
Due date:
% Done:


Estimated time:


Many plugins use different ways to include own lib-code. A common topic I came across is applying patches and consuming hooks. Sometimes the files are require'd having something like

unless FoobarController.included_modules.include? MyPlugin::Patches::FooControllerPatch
  FooController.send(:include, MyPlugin::Patches::FooControllerPatch)

in the bottom.

Sometimes the require is wrapped in Reloader's to_prepare-callback (which seems quite useless to me), other plugins have some kind of apply_patch-method implemented, which does the same as the code above in a more generic manner (IMO the most elegant way to go).

While all these techniques basically work, it often invites to mess up with the auto reloader. When doing some tweaks in the codebase (or even develop my own plugin), I either have to disable foreign plugins or painfully restart the dev-server with every change, when autoloading is not properly implemented. Otherwise I face the often reported problem of "Cannot autoload Constant ..."-Error.

My suggestion is to provide a more unified API for applying patches and consuming hooks.

I could imagine a simple approach e.g. of an appyl_patch function, which forwards the params to an array, which then is processed internally by the ActiveSupport::Reloader.to_prepare callback.

Another (more opinionated) approach is to grab all patches from plugins/.../lib/patches/*.rb, after a plugin was registered, which also was applicable to the hooks.

Anyways, I think there should be some mechanism to provide plugins safely, which do not mess up with the autoloader. This could also catch compatibility-stuff I have also often seen like Rails.version < '5.1' ? ActionDispatch::Callbacks : ActiveSupport::Reloader.to_prepare.

Actions #1

Updated by crypto gopher almost 2 years ago

  1. to_prepare() no longer works as expeted in Rails 6, it is enough to put include() directly in init.rb (#36245)
  2. when including patch it is redundant to check included_modules,

Ruby's default implementation is to add the constants, methods, and module variables of this module to mod if this module has not already been added to mod or one of its ancestors.

so your example can be shortened to:

FooController.include MyPlugin::Patches::FooControllerPatch

Also available in: Atom PDF