agentleFS
dilolabsdilolabs/nosia/.claude/skills/job-patterns/SKILL.md

job-patterns

Skill211 starsChanged 5 months agoMITView on GitHub

Implements shallow background jobs with _later/_now conventions using Solid Queue. Use when adding background processing, async operations, scheduled tasks, or when user mentions jobs, queues, workers, or background processing. WHEN NOT: Business logic implementation (use model-patterns), controller work (use crud-patterns), or mailer delivery (use mailer-patterns).

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Default method can be sync (explicit async)

    line 41
  2. Very specific:

    Always set and reset Current context:

    line 180
  3. Very specific:

    Always: Keep jobs thin (call model methods), follow the _later/_now naming convention (though the _now suffix is not strictly enforced), …

    line 276
  4. Distinctive:

    Ask first: Before putting business logic in jobs, before using Redis/Sidekiq, before running jobs synchronously in production

    line 277
  5. Very specific:

    Never: Put business logic in jobs, use Sidekiq/Resque (use Solid Queue), enqueue jobs in transactions (may not commit), forget …

    line 278

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Job Patterns (37signals)

Jobs orchestrate. Models do the work. Background jobs are thin wrappers around model methods.

Project knowledge

Tech Stack: Rails 8.2 (edge), Solid Queue, ActiveJob Pattern: Thin jobs call model methods; models have _later/_now pairs

Commands:

bin/rails generate job NotifyRecipients        # Generate job
bundle exec rake solid_queue:start             # Run worker
bin/rails runner "puts SolidQueue::Job.count"  # Check queue
bin/rails runner "SolidQueue::Job.destroy_all" # Clear jobs

Why shallow jobs

  • Business logic stays in models (testable, reusable)
  • Jobs are simple orchestrators
  • Easy to run sync or async
  • Can call methods directly in tests
  • Clearer separation of concerns

Why _later/_now convention

  • Clear which version is async
  • Default method can be sync (explicit async)
  • Easy to switch between sync/async
  • Testable (call _now in tests)

Core pattern: shallow job

The job receives a model and calls its _now method. The _now suffix is conventional but not required -- some jobs call the plain method name (e.g., notifiable.notify_recipients).

# app/jobs/notify_recipients_job.rb
class NotifyRecipientsJob < ApplicationJob
  queue_as :default

  def perform(notifiable)
    notifiable.notify_recipients_now
  end
end

The model defines both _later and _now methods:

# In model or concern
def notify_recipients_later
  NotifyRecipientsJob.perform_later(self)
end

def notify_recipients_now
  recipients.each do |recipient|
    next if recipient == creator
    Notification.create!(recipient: recipient, notifiable: self, action: notification_action)
  end
end

# Default to sync
def notify_recipients
  notify_recipients_now
end

# Trigger async from callbacks
after_create_commit :notify_recipients_later

Job patterns

Notification job

class NotifyRecipientsJob < ApplicationJob
  queue_as :default

  def perform(notifiable)
    notifiable.notify_recipients_now
  end
end

Batch processing job

class DeliverBundledNotificationsJob < ApplicationJob
  queue_as :default

  def perform
    Notification::Bundle.deliver_all_now
  end
end

Cleanup job

class SessionCleanupJob < ApplicationJob
  queue_as :low_priority

  def perform
    Session.cleanup_old_sessions_now
  end
end

Event tracking job

class TrackEventJob < ApplicationJob
  queue_as :default

  def perform(eventable, action, options = {})
    eventable.track_event_now(action, options)
  end
end

Broadcasting job

class BroadcastUpdateJob < ApplicationJob
  queue_as :default

  def perform(broadcastable)
    broadcastable.broadcast_update_now
  end
end

External API job

class DispatchWebhookJob < ApplicationJob
  queue_as :webhooks
  retry_on StandardError, wait: :exponentially_longer, attempts: 5

  def perform(webhook, event)
    webhook.dispatch_now(event)
  end
end

Retry and error handling

class DispatchWebhookJob < ApplicationJob
  discard_on Webhook::InvalidUrl                              # Don't retry
  retry_on StandardError, wait: :exponentially_longer, attempts: 5  # Backoff
  retry_on CustomError, wait: 5.minutes, attempts: 3          # Fixed interval

  rescue_from Webhook::Timeout do |exception|
    webhook.mark_as_slow!
    raise exception  # Re-raise to trigger retry
  end

  def perform(webhook, event)
    webhook.dispatch_now(event)
  end
end

Current attributes in jobs

Always set and reset Current context:

class NotifyRecipientsJob < ApplicationJob
  before_perform do |job|
    notifiable = job.arguments.first
    Current.account = notifiable.account
    Current.user = notifiable.creator if notifiable.respond_to?(:creator)
  end

  after_perform { Current.reset }

  def perform(notifiable)
    notifiable.notify_recipients_now
  end
end

Performance patterns

Batch processing

# Enqueue in batches
Card.active.pluck(:id).each_slice(100) do |batch|
  ProcessCardsJob.perform_later(batch)
end

class ProcessCardsJob < ApplicationJob
  def perform(card_ids)
    Card.where(id: card_ids).find_each(&:process_now)
  end
end

Debouncing (avoid duplicate jobs)

def reindex_later
  return if reindex_job_queued?
  ReindexBoardJob.perform_later(id)
end

def reindex_job_queued?
  SolidQueue::Job.exists?(
    job_class: "ReindexBoardJob",
    arguments: [id].to_json,
    finished_at: nil
  )
end

Testing jobs

Test model methods directly (preferred)

class CommentTest < ActiveSupport::TestCase
  test "notify_recipients_now creates notifications" do
    comment = comments(:logo_comment)
    assert_difference -> { Notification.count }, 2 do
      comment.notify_recipients_now
    end
  end
end

Verify job is enqueued

class NotifyRecipientsJobTest < ActiveJob::TestCase
  test "enqueues job" do
    comment = comments(:logo_comment)
    assert_enqueued_with job: NotifyRecipientsJob, args: [comment] do
      NotifyRecipientsJob.perform_later(comment)
    end
  end
end

Verify callbacks enqueue jobs

test "creating comment enqueues notification job" do
  card = cards(:logo)
  assert_enqueued_with job: NotifyRecipientsJob do
    card.comments.create!(body: "Great work!", creator: users(:david))
  end
end

See references/solid-queue.md for Solid Queue configuration and references/recurring-jobs.md for recurring job setup.

Boundaries

  • Always: Keep jobs thin (call model methods), follow the _later/_now naming convention (though the _now suffix is not strictly enforced), put business logic in models, set queue priorities, implement retry strategies, test model methods directly
  • Ask first: Before putting business logic in jobs, before using Redis/Sidekiq, before running jobs synchronously in production
  • Never: Put business logic in jobs, use Sidekiq/Resque (use Solid Queue), enqueue jobs in transactions (may not commit), forget Current.reset in jobs, skip retry strategies for unreliable operations

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.