Class: Moderate::ClassifyJob
- Inherits:
-
ActiveJob::Base
- Object
- ActiveJob::Base
- Moderate::ClassifyJob
- Defined in:
- lib/moderate/jobs/classify_job.rb
Overview
The background worker for ASYNCHRONOUS filter adapters (the :openai adapter, a host’s hosted classifier, the default :image adapter) running in :flag mode.
── Why a job exists at all ──────────────────────────────────────────────────An async adapter does blocking I/O (a moderation API call), which must never run inside the request that saved the content — and CANNOT run inside a validator or an after_commit callback without stalling the write. So the :flag path works in two halves:
1. The model's filter concern (`moderates :field`) lets the write succeed, then
in an after_commit hook enqueues THIS job for any field whose policy uses an
async adapter. (Synchronous adapters like :wordlist skip the job — the
concern classifies inline and files the Flag directly.)
2. This job re-reads the saved value, classifies it through the adapter, and —
if the content is flagged — files (or updates) a Moderate::Flag for the
moderation queue.
── Re-reading the value (deliberate) ────────────────────────────────────────The job is handed the RECORD (GlobalID-serialized by ActiveJob) and the FIELD NAME, not the raw text/image — so it always classifies the CURRENT persisted value. If the record was edited or deleted between enqueue and run, we classify what’s actually there now (or skip a vanished record), never a stale snapshot.
── Idempotency ──────────────────────────────────────────────────────────────Flag creation goes through ‘Moderate::Flag.flag!`, the single builder shared by the synchronous and asynchronous paths. It’s an upsert-by-(flaggable, field, source) so a retried job (ActiveJob retries on transient failures) doesn’t pile up duplicate queue entries for the same content.
── Base class ───────────────────────────────────────────────────────────────We subclass ‘ActiveJob::Base` directly (not the host’s ApplicationJob) so the gem doesn’t depend on a constant that lives in the host app — the same reason the rest of the gem stays host-agnostic. The host configures the queue adapter, retries, and queue name globally as usual.
Instance Method Summary collapse
Instance Method Details
#perform(record, field, adapter: nil) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/moderate/jobs/classify_job.rb', line 47 def perform(record, field, adapter: nil) # The record may have been destroyed between enqueue and execution — nothing # to classify, nothing to flag. (ActiveJob raises DeserializationError for a # GlobalID that no longer resolves; that's rescued at the framework level, but # we also guard a nil here defensively.) return if record.nil? field = field.to_s policy = resolve_policy(record, field, adapter) # If the field's policy is :off (e.g. it was reconfigured to off after the job # was enqueued), there's nothing to do. return if policy.respond_to?(:off?) && policy.off? value = field_value(record, field) return if blank?(value) result = Moderate.classify(value, policy: policy) return unless result.flagged? file_flag(record, field, policy, result, value) end |