Use when writing background jobs or async operations - enforces thin job wrappers (3-5 lines) that delegate to models using _later/_now naming pattern
From vanilla-railsnpx claudepluginhub zemptime/zemptime-marketplace --plugin vanilla-railsThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Jobs are thin wrappers (3-5 lines). ALL business logic lives in models.
# Model concern - WHERE THE LOGIC LIVES
module Card::ClosureNotifications
extend ActiveSupport::Concern
included do
after_update :notify_watchers_later, if: :just_closed?
end
def notify_watchers_later
Card::ClosureNotificationJob.perform_later(self)
end
def notify_watchers_now
watchers.each do |watcher|
CardMailer.closure_notification(watcher, self).deliver_now
Notification.create!(user: watcher, card: self, action: 'closed')
end
end
end
# Job - ONLY delegates (3 lines)
class Card::ClosureNotificationJob < ApplicationJob
def perform(card)
card.notify_watchers_now
end
end
Flow: Callback → _later → enqueue job → job calls _now → logic executes
_now synchronously — no job infrastructure needed_now in console, tests, anywhereMulti-model — primary model orchestrates:
class User::DigestJob < ApplicationJob
def perform(user); user.send_digest_now; end
end
Utility/cleanup — use class methods:
class Session::CleanupJob < ApplicationJob
def perform; Session.cleanup_expired_now; end
end
Error handling — ActiveJob retries + model errors:
class Card::SyncJob < ApplicationJob
retry_on ExternalAPI::Error, wait: 5.minutes
def perform(card); card.sync_to_external_system_now; end
end
| Red flag | Fix |
|---|---|
Job > 5 lines (excluding retry_on) | Move logic to model |
| Business logic in job | Move to _now method on model |
perform(card_id) then Card.find | perform(card) — let ActiveJob serialize |
No _later/_now naming | Add suffixes |
Missing _now method | Always create — needed for testing |
| Job sends emails directly | Model orchestrates, mailer delivers |
| Job has conditionals/loops | Domain logic goes in model |
_later method (enqueues)_now method (logic)