Understanding a conflict between two ActiveSupport.on_load calls

Hello.

Our application depends on two gems: GitHub - dxw/mail-notify: ActionMailer support for the GOV.UK Notify API and GitHub - collectiveidea/audited: Audited (formerly acts_as_audited) is an ORM extension that logs all changes to your Rails models..

The mailer preview index at rails/mailers is broken. It throws ActionView::Template::Error (arguments passed to url_for can't be handled. Please require routes or provide your own implementation). This seems to be because the definition of url_for comes from ActionView::Helpers::UrlHelper. Removing the audited gem makes the preview index work correctly, because url_for then resolves to ActionDispatch::Routing::UrlFor#url_for.

Here is a minimal rails app displaying the behaviour: GitHub - duncanjbrown/audited-bug.

I can make the exception go away by editing these lines of the audited gem in one of two ways.

  1. I can wrap them in a Railtie under an initializer like this: Wrap the ActiveSupport.on_load hook in a railtie · duncanjbrown/audited@fa11351 · GitHub
  2. I can comment out the reference to ActionController::API.

I would like to understand a few things.

  1. Why does this happen?
  2. What has ActionController::API got to do with it?
  3. Why does it manifest in the way it does (the url_for definition)
  4. Is it ever right to set an ActiveSupport.on_load hook outside an initializer (ie is this a mistake in audited?)

Thanks for your help!

From the guides:

Rails code can often be referenced on load of an application. Rails is responsible for the load order of these frameworks, so when you load frameworks, such as ActiveRecord::Base , prematurely you are violating an implicit contract your application has with Rails. Moreover, by loading code such as ActiveRecord::Base on boot of your application you are loading entire frameworks which may slow down your boot time and could cause conflicts with load order and boot of your application.

Load and configuration hooks are the API that allow you to hook into this initialization process without violating the load contract with Rails. This will also mitigate boot performance degradation and avoid conflicts.

When you call ActionController::Base in lib/audited.rb outside of an on_load hook it causes Ruby to look for the definition of that constant and will require it. This causes the entire ActionPack framework to be loaded before boot.

Acts as audited should probably wrap the following line in an on_load hook as well:

1 Like

If would be nice if prematurely calling a framework generates a warning instead of continuing and failing later on. There is the PR:

https://github.com/rails/rails/pull/38024

The url_for method is included here:

If you prematurely initialize ActionController::API the UrlFor module hasn’t yet been included resulting in an error.

Thank you @petrik!

I’ll see if I can submit something like this as a PR to audited: Comparing collectiveidea:master...duncanjbrown:use-railtie-for-on-load · collectiveidea/audited · GitHub. Does that look idiomatic to you? (I haven’t worked with this part of Rails before).

@duncanjbrown Looks fine to me!

1 Like