# Event Sourcing Coder

> Use when building activity logs, syncing external services, or decoupling event creation from processing.

- Skill: `majesticlabs-dev/event-sourcing-coder` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add majesticlabs-dev/event-sourcing-coder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majesticlabs-dev/event-sourcing-coder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majesticlabs-dev (https://skillmd.com/u/majesticlabs-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/majesticlabs-dev/event-sourcing-coder

---


# Event Sourcing for Rails Monoliths

Record significant domain events and dispatch them to specialized handlers - a pragmatic approach to event sourcing without the complexity of full CQRS/ES infrastructure.

## When to Use This Skill

- Building activity feeds or audit trails
- Syncing data to external services (CRMs, analytics, webhooks)
- Automating workflows triggered by domain events
- Decoupling "what happened" from "what to do about it"
- Tracking user actions for analytics or debugging

## When NOT to Use Events

| Scenario | Better Alternative |
|----------|-------------------|
| Simple callbacks on single model | ActiveRecord callbacks |
| Synchronous side effects only | Service objects or ActiveInteraction |
| Need full event replay/rebuilding | Dedicated event sourcing gem (Rails Event Store) |
| Single handler per event | Direct method calls |

## Core Concept

**Decouple recording from processing:**

```
User action → Record Event → Broadcast to Inboxes → Side Effects
                  ↓
              Queryable
              (audit trail)
```

## Setup

### Migration

```ruby
class CreateIssueEvents < ActiveRecord::Migration[8.0]
  def change
    create_table :issue_events do |t|
      t.references :issue, null: false, foreign_key: true
      t.references :actor, null: false, foreign_key: { to_table: :users }
      t.string :action, null: false
      t.jsonb :metadata, default: {}
      t.timestamps
    end

    add_index :issue_events, :action
    add_index :issue_events, :created_at
  end
end
```

### Event Model

```ruby
# app/models/issue/event.rb
module Issue::Event
  extend ActiveSupport::Concern

  ACTIONS = %w[
    created
    assigned
    status_changed
    commented
    closed
    reopened
  ].freeze

  included do
    belongs_to :issue
    belongs_to :actor, class_name: "User"

    validates :action, presence: true, inclusion: { in: ACTIONS }

    after_commit :broadcast_to_inboxes, on: :create
  end

  private

  def broadcast_to_inboxes
    Issue::Event::BroadcastJob.perform_later(self)
  end
end

class Issue::Event < ApplicationRecord
  include Issue::Event
end
```

### Recording Events

```ruby
# app/models/issue.rb
class Issue < ApplicationRecord
  has_many :events, class_name: "Issue::Event", dependent: :destroy

  def record_event!(action:, actor:, metadata: {}, throttle: nil)
    return if throttle && recently_recorded?(action, throttle)

    events.create!(
      action: action,
      actor: actor,
      metadata: metadata
    )
  end

  private

  def recently_recorded?(action, duration)
    events.where(action: action)
          .where("created_at > ?", duration.ago)
          .exists?
  end
end
```

### Usage in Application

```ruby
# In a controller or interaction
issue.record_event!(
  action: "status_changed",
  actor: current_user,
  metadata: { from: "open", to: "in_progress" }
)

# With throttling (prevent duplicate events within timeframe)
issue.record_event!(
  action: "viewed",
  actor: current_user,
  throttle: 5.minutes
)
```

## Detailed Reference

Load [detailed-reference.md](references/detailed-reference.md) for extended examples, templates, and advanced patterns.

