From bf48cded6534fa137a499a0bd1b7b4c10b5fc9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Lo=CC=81pez?= Date: Wed, 27 May 2026 15:34:56 +0200 Subject: [PATCH 1/2] Fix MoneySerializer not registered on Rails 8.1+ (#779) 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) --- CHANGELOG.md | 1 + lib/money-rails/hooks.rb | 22 ++++++++++++++-------- spec/active_job/money_serializer_spec.rb | 8 ++++++++ spec/dummy/config/application.rb | 8 ++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43516654e..c6100766a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Register the Active Record and Action View integrations when the gem is required, instead of from the railtie initializer - Add a top-level `null` option to the `add_monetize`/`monetize` migration helpers (#775) +- Fix `MoneyRails::ActiveJob::MoneySerializer` not being registered on Rails 8.1+ (#779) ## 3.0.0 diff --git a/lib/money-rails/hooks.rb b/lib/money-rails/hooks.rb index 178fa2ed6..9c27a682f 100644 --- a/lib/money-rails/hooks.rb +++ b/lib/money-rails/hooks.rb @@ -37,14 +37,20 @@ def self.init require "money-rails/mongoid/money" end - # For ActiveSupport - ActiveSupport.on_load(:active_job) do - if defined?(::ActiveJob::Serializers) - require "money-rails/active_job/money_serializer" - Rails.application.config.active_job.tap do |config| - config.custom_serializers ||= [] - config.custom_serializers << MoneyRails::ActiveJob::MoneySerializer - end + # For Active Job + # + # Registered during initialization rather than inside an + # `ActiveSupport.on_load(:active_job)` block so the serializer is present in + # `config.active_job.custom_serializers` before Rails consumes the list. Rails + # reads it at different points across versions (`after_initialize` on <= 8.0, + # `on_load(:active_job)` on 8.1.0/8.1.1, `on_load(:active_job_arguments)` on 8.1.2+), + # any of which an `:active_job` hook here could lose the race to. See issue #779 and + # rails/rails commits b7cd3018ef (8.1.0) and c0dc92e9e9 (8.1.2). + if defined?(::ActiveJob::Serializers) + require "money-rails/active_job/money_serializer" + Rails.application.config.active_job.tap do |config| + config.custom_serializers ||= [] + config.custom_serializers << MoneyRails::ActiveJob::MoneySerializer end end end diff --git a/spec/active_job/money_serializer_spec.rb b/spec/active_job/money_serializer_spec.rb index 439d597d7..743e304e8 100644 --- a/spec/active_job/money_serializer_spec.rb +++ b/spec/active_job/money_serializer_spec.rb @@ -25,5 +25,13 @@ describe "#deserialize" do it { expect(described_class.deserialize(serialized_money)).to eq(money) } end + + describe "Active Job registration" do + it "round-trips Money through Active Job argument serialization" do + serialized = ActiveJob::Arguments.serialize([money]) + + expect(ActiveJob::Arguments.deserialize(serialized)).to eq([money]) + end + end end end diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index 2a5357c0e..a5bb7045d 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -4,6 +4,14 @@ require "logger" require "active_record/railtie" unless ENV["BUNDLE_GEMFILE"]&.include?("mongoid") +# The Mongoid gemfiles depend on Action Pack only, not the full rails gem, so the +# activejob gem may be unavailable. Load it only when present (the serializer specs +# are guarded by `defined?(ActiveJob::Serializers)`). +begin + require "active_job/railtie" +rescue LoadError + nil +end require "action_controller/railtie" Bundler.require From b162dffb95fd4c515c04afe87f04ca5af6baee29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Lo=CC=81pez?= Date: Thu, 2 Jul 2026 16:16:07 +0200 Subject: [PATCH 2/2] Register MoneySerializer directly with ActiveJob::Serializers 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 --- CHANGELOG.md | 2 +- lib/money-rails/hooks.rb | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6100766a..ea24dcb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Register the Active Record and Action View integrations when the gem is required, instead of from the railtie initializer - Add a top-level `null` option to the `add_monetize`/`monetize` migration helpers (#775) -- Fix `MoneyRails::ActiveJob::MoneySerializer` not being registered on Rails 8.1+ (#779) +- Fix `MoneyRails::ActiveJob::MoneySerializer` not being registered on Rails 8.1+, and on older versions without eager loading (#779) ## 3.0.0 diff --git a/lib/money-rails/hooks.rb b/lib/money-rails/hooks.rb index 9c27a682f..bc8e009b9 100644 --- a/lib/money-rails/hooks.rb +++ b/lib/money-rails/hooks.rb @@ -38,20 +38,9 @@ def self.init end # For Active Job - # - # Registered during initialization rather than inside an - # `ActiveSupport.on_load(:active_job)` block so the serializer is present in - # `config.active_job.custom_serializers` before Rails consumes the list. Rails - # reads it at different points across versions (`after_initialize` on <= 8.0, - # `on_load(:active_job)` on 8.1.0/8.1.1, `on_load(:active_job_arguments)` on 8.1.2+), - # any of which an `:active_job` hook here could lose the race to. See issue #779 and - # rails/rails commits b7cd3018ef (8.1.0) and c0dc92e9e9 (8.1.2). if defined?(::ActiveJob::Serializers) require "money-rails/active_job/money_serializer" - Rails.application.config.active_job.tap do |config| - config.custom_serializers ||= [] - config.custom_serializers << MoneyRails::ActiveJob::MoneySerializer - end + ::ActiveJob::Serializers.add_serializers(MoneyRails::ActiveJob::MoneySerializer) end end end