Skip to content

Register framework integrations when the gem is required - #786

Merged
yukideluxe merged 2 commits into
RubyMoney:mainfrom
mokevnin:register-framework-hooks-at-require-time
Sep 29, 2026
Merged

yukideluxe merged 2 commits into
RubyMoney:mainfrom
mokevnin:register-framework-hooks-at-require-time

Conversation

@mokevnin

@mokevnin mokevnin commented Sep 2, 2026

Copy link
Copy Markdown

What

Move the two framework mixins — MoneyRails::ActiveRecord::Monetizable and MoneyRails::ActionViewExtension — from MoneyRails::Hooks.init to require time in lib/money-rails.rb.

Why

Hooks.init is only called from the railtie initializer, and it is what registers the ActiveSupport.on_load(:active_record) / on_load(:action_view) blocks. Outside a full Rails boot nothing registers them, so monetize ends up defined on no class at all:

require "active_record"
require "money-rails"
ActiveRecord::Base.respond_to?(:monetize) # => false

That makes the Active Record integration invisible to everything that inspects the code rather than boots a server: doc generators, editor autocomplete, static analysis, and a plain require "money-rails" in a script or a test harness. It is the same class of problem as #614 / #727, where MoneyRails::ActionViewExtension was unavailable until a callback happened to fire.

Registering an on_load hook is lazy and cheap — it does not load Active Record or Action View, it only queues the block until something else loads them. So people using the gem without Rails (#470, #291) pay nothing for the registration, and get monetize from the bare require.

require "active_support/lazy_load_hooks" is added explicitly rather than relying on money/monetize to have pulled Active Support in; activesupport is already a runtime dependency.

What stays put

The migration extensions stay in Hooks.init. Choosing between the PostgreSQL and generic variants reads connection_db_config, which needs a configured database — exactly what the railtie's after: "active_record.initialize_database" guarantees, and not something a bare require can promise.

Hooks.init remains public and its README-documented use for non-Rails apps is unchanged. The mixins it no longer performs are already registered by the require it necessarily follows, and a repeated include of the same module is a no-op regardless.

No behavior change — Rails apps load the same modules into the same classes, in the same order.

Tested

  • bundle exec rake spec green on active_record7.0, 7.1, 7.2, 8.0 and 8.1 (Ruby 4.0), 278 examples each.
  • bundle exec rubocop clean.
  • The new spec shells out to a fresh Ruby that requires money-rails before active_record, asserts the require did not eagerly load Active Record, then asserts monetize is there. It fails on main and passes here.
  • Could not run the Mongoid gemfiles (no local mongod) or the JRuby cell.

sunny
sunny previously approved these changes Sep 4, 2026

@sunny sunny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

`monetize` and the Action View helpers were only mixed in from the
railtie initializer, via `MoneyRails::Hooks.init`. Outside a full Rails
boot nothing registered those hooks, so `monetize` existed on no class:
`ActiveRecord::Base.respond_to?(:monetize)` was false, and anything that
inspects code rather than runs a server — documentation generators,
editor autocomplete, static analysis, a plain `require "money-rails"` in
a script or a test harness — saw an Active Record integration that was
simply absent. It is the same class of problem as RubyMoneyGH-614: an integration
invisible until an initializer happens to run.

Move the two framework mixins to require time in `lib/money-rails.rb`.
Registering an `ActiveSupport.on_load` hook is lazy and cheap: it does
not load Active Record or Action View, it only queues the block until
something else loads them, so non-Rails users pay nothing.

The migration extensions stay in `Hooks.init`, because choosing between
the PostgreSQL and generic variants reads `connection_db_config` and so
needs a configured database — which is what the railtie's
`after: "active_record.initialize_database"` guarantees. `Hooks.init`
remains public and its documented use for non-Rails apps is unchanged;
the mixins it no longer performs are already registered by the require
it necessarily follows, and a repeated `include` is a no-op anyway.

No behavior change for Rails apps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mokevnin
mokevnin force-pushed the register-framework-hooks-at-require-time branch from 064feaf to ee3a233 Compare September 17, 2026 15:29
@mokevnin

Copy link
Copy Markdown
Author

Rebased onto main so the branch is no longer behind — it was only #787 (binstubs), zero overlap with this diff, so the change itself is byte-identical to what you approved.

Verified locally on the rebased commit: bin/rake spec (278 examples, 0 failures) and bin/rubocop (77 files, no offenses).

CI is sitting at action_required — it needs a maintainer to approve the workflow run.

@yukideluxe yukideluxe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I fixed the changelog conflict 😊

@yukideluxe
yukideluxe merged commit 2bfcf09 into RubyMoney:main Sep 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants