Skip to content

Fix :flag async adapters (route through ClassifyJob) + out-of-the-box Active Storage attachment filtering#3

Merged
rameerez merged 1 commit into
mainfrom
fix/flag-async-adapters-via-classify-job
Jul 10, 2026
Merged

Fix :flag async adapters (route through ClassifyJob) + out-of-the-box Active Storage attachment filtering#3
rameerez merged 1 commit into
mainfrom
fix/flag-async-adapters-via-classify-job

Conversation

@rameerez

Copy link
Copy Markdown
Owner

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::ClassifyJob

beta1's :flag after_commit called Moderate.classify inline for every adapter, including ones declaring synchronous? == false. Consequences:

  • a remote moderation API's network call ran inside the request that saved the content (1–5s stall per save, exactly what the docs say must never happen);
  • Moderate::ClassifyJob — whose header, and filters/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 :block validator's: async only when the adapter explicitly answers synchronous? == false) and enqueues ClassifyJob instead of classifying inline. The job re-reads the persisted value and files the Flag through the same Flag.flag! builder. Hosts that worked around the gap (enqueue-it-yourself + short-circuit moderation_field_changed_for_commit?) can delete both workarounds — CarHey will, on the next release bump.

2. moderates <attachment> works out of the box now

Previously every host had to hand-roll the three-method seam for a has_one_attached field (AR dirty tracking can't see attachment writes, and Active Storage clears attachment_changes before after_commit). Now the concern:

  • snapshots filtered attachment writes in a before_save (one-shot, consumed at commit, cleared through the existing moderation_field_committed ensure-hook);
  • treats an unattached ActiveStorage::Attached proxy as blank in both the concern and ClassifyJob (covers the purge-between-enqueue-and-run race).

The overridable seam remains for richer cases. The dummy Comment now 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

…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
rameerez merged commit 2c348b8 into main Jul 10, 2026
14 of 15 checks passed
@rameerez
rameerez deleted the fix/flag-async-adapters-via-classify-job branch July 10, 2026 18:41
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
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.

1 participant