Repository navigation
Fix MoneySerializer not registered on Rails 8.1.0+ - #780
yukideluxe wants to merge 2 commits into
Conversation
2a02d27 to
80c98f1
Compare
|
@jaredmoody reported my original fix did not solve the problem and @sk- said the current code doesn't work when 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 Could somebody help me confirm?! 🙏🏻 Ping @sunny PS: CI failures seem unrelated, I will fix in |
wwahammy
left a comment
There was a problem hiding this comment.
@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.
3114946 to
0095293
Compare
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>
0095293 to
b162dff
Compare
|
@wwahammy sorry the delay!
The name is misleading! CI runs the whole AR test suite 🙏🏻 |
Fixes #779
Passing a
Moneyas an Active Job argument can fail withActiveJob::SerializationErrorbecauseMoneyRails::ActiveJob::MoneySerializernever gets registered. This happens on Rails 8.1, and also on Rails ≤ 8.0 whenActiveJob::Baseis loaded after boot (for example withconfig.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_serializersinside anActiveSupport.on_load(:active_job)block. Rails reads that list once, and when it reads it depends on the version:custom_serializersinconfig.after_initializeActiveJob::Baseloads after boot (e.g.eager_load = false), money-rails appends after Rails has already read the liston_load(:active_job)on_load(:active_job_arguments):active_jobhook happens to fire before the:active_job_argumentsoneSwitching 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:This skips
config.active_job.custom_serializersentirely, so it no longer matters when Rails reads it. It's safe because on every supported version Rails only adds the config list toActiveJob::Serializers(add_serializers) and never replaces it, so a serializer registered earlier stays registered. It also no longer referencesRails.application, so theHooks.initcall documented in the README for non-Rails apps registers the serializer too.Tests
spec/active_job/money_serializer_spec.rbround-trips aMoneythroughActiveJob::Arguments.serialize/deserialize.active_job/railtie(inside arescue LoadError, because the Mongoid gemfiles don't include Active Job) so these specs actually run.active_record7.0throughactive_record8.1gemfiles.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