Fix :flag async adapters (route through ClassifyJob) + out-of-the-box Active Storage attachment filtering#3
Merged
Conversation
…nt support Two host-facing fixes found while wiring CarHey's avatar AI classifier (the first real-world async image adapter): 1. ASYNC ADAPTERS NEVER RAN IN THE JOB. The :flag after_commit called Moderate.classify INLINE for every adapter - including ones declaring synchronous? == false - so a remote moderation API's network call ran inside the request that saved the content, and Moderate::ClassifyJob (whose own docs, and filters/base.rb's, promised the routing) was dead code nothing enqueued. The concern now probes the new Configuration#adapter_async? (same predicate the :block validator uses) and enqueues ClassifyJob instead; the job re-reads the persisted value and files the Flag itself. Hosts that worked around this by enqueuing the job themselves + short-circuiting moderation_field_changed_for_commit? can delete both workarounds. 2. ACTIVE STORAGE ATTACHMENTS NOW WORK OUT OF THE BOX. `moderates :avatar, with: :img, mode: :flag` on a has_one_attached model previously required every host to hand-roll the 3-method seam (AR dirty tracking can't see attachment writes, and Active Storage clears attachment_changes before after_commit). The concern now snapshots filtered attachment writes in a before_save, consumes the one-shot snapshot at commit (cleared via moderation_field_committed, which the ensure already guaranteed), and both the concern and ClassifyJob treat an unattached ActiveStorage::Attached proxy as blank - covering the purge-between-enqueue-and-run race. The overridable seam stays for richer cases; the dummy Comment now carries BOTH shapes (:image = custom seam, :photo = native). Tests: existing image tests updated to the enqueue+perform shape; new coverage for never-inline, native tracking + re-save no-spam, vanished- attachment no-op, and adapter_async?. 205 runs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMa8bEvYfi9Dr8RYhhfKze
rameerez
added a commit
that referenced
this pull request
Jul 10, 2026
CHANGELOG: fold the since-beta1 deltas (#3 async-adapter routing fix + Active Storage attachment filtering, #4 Flag close sugar) into a proper [1.0.0.beta2] section; bump Moderate::VERSION. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMa8bEvYfi9Dr8RYhhfKze
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while wiring CarHey's avatar AI classifier — the first real-world async image adapter against the released
1.0.0.beta1.1. Async adapters never actually ran in
Moderate::ClassifyJobbeta1's
:flagafter_commit calledModerate.classifyinline for every adapter, including ones declaringsynchronous? == false. Consequences:Moderate::ClassifyJob— whose header, andfilters/base.rb's comments, promise this routing — was dead code nothing enqueued.Fix: the concern probes the new public
Configuration#adapter_async?(identical predicate to the:blockvalidator's: async only when the adapter explicitly answerssynchronous? == false) and enqueuesClassifyJobinstead of classifying inline. The job re-reads the persisted value and files the Flag through the sameFlag.flag!builder. Hosts that worked around the gap (enqueue-it-yourself + short-circuitmoderation_field_changed_for_commit?) can delete both workarounds — CarHey will, on the next release bump.2.
moderates <attachment>works out of the box nowPreviously every host had to hand-roll the three-method seam for a
has_one_attachedfield (AR dirty tracking can't see attachment writes, and Active Storage clearsattachment_changesbefore after_commit). Now the concern:before_save(one-shot, consumed at commit, cleared through the existingmoderation_field_committedensure-hook);ActiveStorage::Attachedproxy as blank in both the concern andClassifyJob(covers the purge-between-enqueue-and-run race).The overridable seam remains for richer cases. The dummy
Commentnow carries both shapes::image(custom seam, unchanged) and a new:photo(native, zero overrides).Tests
205 runs, 0 failures. Existing image tests updated to the enqueue+perform shape; new coverage: async-never-inline, native attachment tracking + re-save-no-spam, vanished-attachment no-op,
adapter_async?. Rubocop: no new offenses beyond the repo's pre-existing Metrics baseline.Release note
This is a behavior change for any beta1 host silently relying on inline classification of an async adapter (there shouldn't be any — that path blocked requests). Suggest shipping as
1.0.0.beta2; CHANGELOG entries added under the unreleased 1.0.0 section.🤖 Generated with Claude Code
https://claude.ai/code/session_01UMa8bEvYfi9Dr8RYhhfKze