Skip to content

Fix MoneySerializer not registered on Rails 8.1.0+ - #780

Open
yukideluxe wants to merge 2 commits into
mainfrom
fix/money-serializer-activejob-registration
Open

yukideluxe wants to merge 2 commits into
mainfrom
fix/money-serializer-activejob-registration

Conversation

@yukideluxe

@yukideluxe yukideluxe commented May 27, 2026 •

Copy link
Copy Markdown
Member

Fixes #779

Passing a Money as an Active Job argument can fail with ActiveJob::SerializationError because MoneyRails::ActiveJob::MoneySerializer never gets registered. This happens on Rails 8.1, and also on Rails ≤ 8.0 when ActiveJob::Base is loaded after boot (for example with config.eager_load = false). With this change the serializer is registered on every boot, on Rails 7.0 through 8.1, however Active Job is loaded.

Root cause

money-rails registered its serializer by appending to config.active_job.custom_serializers inside an ActiveSupport.on_load(:active_job) block. Rails reads that list once, and when it reads it depends on the version:

Rails Rails reads custom_serializers in What went wrong
≤ 8.0 config.after_initialize If ActiveJob::Base loads after boot (e.g. eager_load = false), money-rails appends after Rails has already read the list
8.1.0 / 8.1.1 (rails/rails@b7cd3018ef) on_load(:active_job) Same hook as money-rails. Rails registers its callback first, so it always reads the list before money-rails appends
8.1.2+ (rails/rails@c0dc92e9e9) on_load(:active_job_arguments) Works only if the :active_job hook happens to fire before the :active_job_arguments one

Switching money-rails to on_load(:active_job_arguments) would not help: that hook does not exist before 8.1.2, and it still depends on callback order.

Fix

MoneyRails::Hooks.init (called from the railtie initializer) now registers the serializer directly:

if defined?(::ActiveJob::Serializers)
  require "money-rails/active_job/money_serializer"
  ::ActiveJob::Serializers.add_serializers(MoneyRails::ActiveJob::MoneySerializer)
end

This skips config.active_job.custom_serializers entirely, so it no longer matters when Rails reads it. It's safe because on every supported version Rails only adds the config list to ActiveJob::Serializers (add_serializers) and never replaces it, so a serializer registered earlier stays registered. It also no longer references Rails.application, so the Hooks.init call documented in the README for non-Rails apps registers the serializer too.

Tests

  • New example in spec/active_job/money_serializer_spec.rb round-trips a Money through ActiveJob::Arguments.serialize / deserialize.
  • The dummy app now requires active_job/railtie (inside a rescue LoadError, because the Mongoid gemfiles don't include Active Job) so these specs actually run.
  • The full suite passes on the active_record7.0 through active_record8.1 gemfiles.

The new example fails without the fix on Rails 7.0 and 8.0. On Rails 8.1.2 it passes even without the fix, because the dummy app happens to fire the hooks in the working order. CI deletes the lockfiles and installs the newest 8.1 patch (8.1.4 in the latest run), and I haven't checked whether the example fails there without the fix. So we can't count on CI to catch an 8.1 regression, and 8.1.0/8.1.1 aren't tested at all.

@jaredmoody would you mind checking whether this fixes what you are seeing? Thanks!

🤖 Generated with Claude Code

@yukideluxe yukideluxe changed the title Fix MoneySerializer not registered on Rails 8.1.2+ Fix MoneySerializer not registered on Rails 8.1.0+ May 27, 2026
Comment thread lib/money-rails/hooks.rb
@yukideluxe
yukideluxe force-pushed the fix/money-serializer-activejob-registration branch from 2a02d27 to 80c98f1 Compare May 28, 2026 08:51
sunny
sunny previously approved these changes May 28, 2026
@yukideluxe

Copy link
Copy Markdown
Member Author

@jaredmoody reported my original fix did not solve the problem and @sk- said the current code doesn't work when config.eager_mode = false, so I've been drilling Claude to test all the combos 😳

I must admit this is going a bit over my head and my main goal is to simplify the code and avoid Rails version checks at all costs, so after a bunch of back and forth I think the whole thing can be simplified by just adding the MoneySerializer directly into ::ActiveJob::Serializers and stop using the custom_serializers config that is consumed at different moments of boot time depending on the Rails version used... I think this also addresses @wwahammy's concern 🤔

Could somebody help me confirm?! 🙏🏻

Ping @sunny

PS: CI failures seem unrelated, I will fix in main before merging

@wwahammy wwahammy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@yukideluxe I think this all makes sense to me. I think it looks good.

One concern I do have is that the the CI only appeared to test on MongoDB so I'd be worried about whether the tests are missing anything due to that.

@yukideluxe
yukideluxe force-pushed the fix/money-serializer-activejob-registration branch from 3114946 to 0095293 Compare July 6, 2026 17:42
yukideluxe and others added 2 commits September 29, 2026 11:56
money-rails registered its Active Job serializer by appending to
`config.active_job.custom_serializers` inside an `ActiveSupport.on_load(:active_job)`
block. Rails 8.1.0 (rails/rails@b7cd3018ef) moved consumption of that list out of
`after_initialize` into a load hook, and 8.1.2 (rails/rails@c0dc92e9e9) changed that
hook to `on_load(:active_job_arguments)`. Either way money-rails ends up appending the
serializer after Rails has already read the list, so it is never registered:

- On 8.1.0/8.1.1 Rails reads the list in `on_load(:active_job)` -- the same hook
  money-rails uses -- and Rails' framework callback runs first, so normal job
  enqueuing of `Money` arguments breaks.
- On 8.1.2+ Rails reads it in `on_load(:active_job_arguments)`; normal enqueuing works
  again, but `ActiveJob::Arguments.serialize` called without loading `ActiveJob::Base`
  (a documented public path) gets no custom serializers.

Register the serializer during initialization instead, so it is present in
`config.active_job.custom_serializers` before Rails consumes the list -- regardless of
how Rails consumes it or which of `ActiveJob::Base`/`ActiveJob::Arguments` loads first.

The dummy test app now loads `active_job/railtie` (it previously didn't, so the
serializer specs were silently skipped). Adds an integration test that round-trips a
`Money` through `ActiveJob::Arguments`, which runs under every supported Rails
gemfile (7.0-8.1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Appending to config.active_job.custom_serializers during initialization
is not enough: Rails consumes that list exactly once, from a point that
varies by version (after_initialize on <= 8.0, the :active_job load
hook on 8.1.0/8.1.1, the :active_job_arguments load hook on 8.1.2+) and
that can fire mid-boot, before money-rails' initializer runs - e.g. any
boot-time reference to ActiveJob::Arguments,
ActiveJob::SerializationError or ActiveJob::DeserializationError
triggers the 8.1.2+ hook, leaving the serializer unregistered.

Instead, add the serializer directly to the live ActiveJob::Serializers
registry, which nothing resets, so registration during initialization
takes effect no matter when (or whether) each Rails version reads the
config list. This also stops touching
Rails.application.config.active_job, which does not exist when the
activejob gem is loaded without its railtie.

Verified against Rails 7.0.10, 7.1.6, 7.2.3.1, 8.0.5, 8.1.0, 8.1.1,
8.1.2 and 8.1.3, with Active Job loaded lazily, eagerly, and before
application boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yukideluxe
yukideluxe force-pushed the fix/money-serializer-activejob-registration branch from 0095293 to b162dff Compare September 29, 2026 09:58
@yukideluxe

Copy link
Copy Markdown
Member Author

@wwahammy sorry the delay!

One concern I do have is that the the CI only appeared to test on MongoDB so I'd be worried about whether the tests are missing anything due to that.

The name is misleading! CI runs the whole AR test suite 🙏🏻

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.

MoneySerializer not loaded in Rails 8.0+

3 participants