Agent skill

Read Model

by RailsEventStore in RailsEventStore/ecommerce

Build a new read model following project conventions (event handlers, tests, migration, configuration)

MITAuto-check passedTesting & QA

Install Read Model

skills CLI
$ npx skills add RailsEventStore/ecommerce --skill read-model -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install RailsEventStore/ecommerce read-model --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/RailsEventStore/ecommerce.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/read-model .claude/skills/read-model && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
read-model
GitHub stars
507
Token cost
~3k tokens
SKILL.md length
1,060 words
Files
1
Skills in repo
8
Repo updated
First seen
Licence
MIT

At a glance

Build a new read model following project conventions (event handlers, tests, migration, configuration)

  • Works in 9 steps: Gather requirements → Write tests first (TDD) → Create the database migration → …
  • Testing & QA work in your project
  • SKILL.md covers When to use, Working Directory, Step-by-step process and Key conventions
  • Calls rails, make and bundle

What it does

Read Model is an agent skill from RailsEventStore/ecommerce. Build a new read model following project conventions (event handlers, tests, migration, configuration)

Its SKILL.md is about 3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Testing & QA. The repository describes itself as: Application with CQRS and Event Sourcing built on Rails and Rails Event Store. The licence is MIT.

When your agent uses it

  • Testing & QA work in your project

Example prompts

  • “/read-model”

Workflow steps

9 steps, taken from the step headings in SKILL.md.

  1. Gather requirements
  2. Write tests first (TDD)
  3. Create the database migration
  4. Create the read model module
  5. EventHandler rules
  6. Facade methods
  7. Register in lib/configuration.rb
  8. Add to mutation testing
  9. Run verification

What it can do on your machine

Read from SKILL.md and the folder at commit 42c6c23. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • rails
    • make
    • bundle

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Read Model loads about 3k tokens when it runs. Until then it costs about 28 tokens; SKILL.md has 1,060 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~28
When it runs · the whole SKILL.md, loaded when a task matches
~3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from RailsEventStore/ecommerce at commit 42c6c23, republished under its MIT licence (© RailsEventStore). 1,060 words, ~3,005 tokens.

Download SKILL.mdSave it as .claude/skills/read-model/SKILL.md (or your agent's skills folder).
name
read-model
description
Build a new read model following project conventions (event handlers, tests, migration, configuration)

Read Model Builder

When to use

Use this skill when asked to create a new read model or add event handlers to an existing read model in any Rails application under apps/.

Working Directory

Determine which app the read model belongs to. Default is apps/rails_application/ unless the user specifies another app (e.g. apps/crm/, apps/todo_mvc/). All paths below are relative to the target app directory.

Step-by-step process

1. Gather requirements

Before writing any code, clarify:

  • The module name for the read model — always ask the user for their preferred name before creating any files. Read-model naming is a product/domain decision the user cares about (e.g. Feed vs PublicFeed, Timeline vs HomeTimeline), and renaming later touches the directory, module, test, cover/.mutant.yml entries, controller references and Configuration wiring. Propose one or two candidate names with a one-line rationale (what UI view / query it serves, and how it contrasts with sibling read models), then let the user confirm or override. Do not derive the name silently from the events or the feature description.
  • Which domain events it will subscribe to (e.g. Catalog::ProductAdded, Ordering::OrderPlaced)
  • What data needs to be stored and queried
  • What facade methods the rest of the app needs — only add facade methods that are actually used by controllers/views, not speculative ones
2. Write tests first (TDD)

Create a single test file at test/{module_name}/{module_name}_test.rb (relative to the app directory).

Test file conventions:

ruby
# test/{module_name}/{module_name}_test.rb
require "test_helper"

module ModuleName
  class ModuleNameTest < InMemoryTestCase
    cover "ModuleName*"

    def test_record_created
      create_record(record_id)

      assert_equal(1, ModuleName.facade_method(store_id).count)
    end

    def test_record_updated
      create_record(record_id)
      create_record(other_record_id)
      update_record(record_id, "new value")

      result = ModuleName.facade_method(store_id).find_by!(uid: record_id)
      assert_equal("new value", result.attribute)
      assert_nil(ModuleName.facade_method(store_id).find_by!(uid: other_record_id).attribute)
    end

    private

    def event_store
      Rails.configuration.event_store
    end

    def record_id
      @record_id ||= SecureRandom.uuid
    end

    def other_record_id
      @other_record_id ||= SecureRandom.uuid
    end

    def store_id
      @store_id ||= SecureRandom.uuid
    end

    def create_record(rid, sid = store_id)
      event_store.publish(DomainContext::RecordCreated.new(data: { record_id: rid }))
      event_store.publish(Stores::RecordRegistered.new(data: { record_id: rid, store_id: sid }))
    end

    def update_record(rid, value)
      event_store.publish(DomainContext::RecordUpdated.new(data: { record_id: rid, value: value }))
    end
  end
end

Test rules:

  • Inherit from InMemoryTestCase
  • Use cover "ModuleName*" for mutation testing
  • In rails_application, override configure to load only the read model's own configuration:
    ruby
    def configure(event_store, _command_bus)
      ModuleName::Configuration.new.call(event_store)
    end
  • In other apps (e.g. todo_mvc), the full Configuration is loaded in before_setup — no override needed if the app only has a few read models
  • Test via event_store.publish(event) to trigger handlers
  • Assert using facade methods, never access ActiveRecord directly
  • Use assert_equal(expected, actual) with parentheses always
  • Single test file per read model — keep all handler tests together
  • No comments in tests
  • Event flows must reflect the real application flow — include Stores::*Registered events for store assignment, Crm::CustomerRegistered before customer assignment, etc.
  • Use helper methods (e.g. create_record, register_customer) to express realistic event sequences
  • Always test with multiple records to kill find_by → Model.update! mutations
  • If a read model test needs Ecommerce::Configuration or Processes::Configuration to pass, that's a smell — the test is probably using run_command instead of publishing events directly, or the read model depends on another read model
3. Create the database migration
ruby
# db/migrate/YYYYMMDDHHMMSS_create_{table_name}.rb
class CreateTableName < ActiveRecord::Migration[8.0]
  def change
    create_table :{table_name} do |t|
      t.uuid :some_uuid_column
      t.string :name
      t.decimal :amount

      t.timestamps
    end
  end
end

Run rails db:migrate after creating.

4. Create the read model module

Everything goes in one file: app/read_models/{module_name}/configuration.rb. It contains:

  • ActiveRecord model class(es) with private_constant
  • Module-level facade methods (only those used by controllers/views)
  • EventHandler class with all event handling logic
  • Configuration class that wires event subscriptions

Three patterns exist in the codebase:

Pattern A: EventHandler with case/when (preferred for custom logic)

Use when events require different handling logic. All handlers go in a single EventHandler class using case event.

ruby
# app/read_models/{module_name}/configuration.rb
module ModuleName
  class Record < ApplicationRecord
    self.table_name = "table_name"
  end

  private_constant :Record

  def self.facade_method(store_id)
    Record.where(store_id: store_id)
  end

  class EventHandler
    def call(event)
      case event
      when DomainContext::RecordCreated
        Record.create!(uid: event.data.fetch(:record_id))
      when Stores::RecordRegistered
        find_record(event).update!(store_id: event.data.fetch(:store_id))
      when DomainContext::RecordUpdated
        find_record(event).update!(attribute: event.data.fetch(:attribute))
      end
    end

    private

    def find_record(event)
      Record.find_by!(uid: event.data.fetch(:record_id))
    end
  end

  class Configuration
    def call(event_store)
      event_store.subscribe(EventHandler.new, to: [
        DomainContext::RecordCreated,
        Stores::RecordRegistered,
        DomainContext::RecordUpdated
      ])
    end
  end
end
Pattern B: SingleTableReadModel (event_store passed to initialize)

Use when the read model is a simple projection that copies event attributes to a single table.

ruby
# app/read_models/{module_name}/configuration.rb
module ModuleName
  class Record < ApplicationRecord
    self.table_name = "table_name"
  end

  private_constant :Record

  def self.facade_method(id)
    Record.where(some_column: id)
  end

  class Configuration
    def initialize(event_store)
      @read_model = SingleTableReadModel.new(event_store, Record, :record_id)
      @event_store = event_store
    end

    def call
      @read_model.subscribe_create(DomainContext::RecordCreated)
      @read_model.subscribe_copy(DomainContext::NameSet, :name)
      @read_model.subscribe_copy(DomainContext::PriceSet, :price)
    end
  end
end
Pattern C: Separate handler classes (legacy)

Some older read models still use one class per event type in separate files. When modifying these, prefer consolidating into Pattern A.

5. EventHandler rules
  • Always use event.data.fetch(:key), never event.data[:key] or event[:key]
  • Single EventHandler class with case event — no separate files per event type
  • Always use find_by! for record lookups — records must exist because events follow the real application flow (e.g., OfferDrafted always comes before OrderRegistered)
  • Never use find_by with &. safe navigation — this hides bugs. If a record is missing, it means the test or event flow is wrong, not that the handler should silently skip
  • No return unless record guards — use find_by! instead
  • No comments
  • No named params in method calls unless required
  • No local variables, prefer method calls
  • Extract shared find_* methods as private helpers for reusability
Show full SKILL.md (433 more words)Show less
5a. Denormalization rules

When a read model copies data from one entity into another table (e.g., customer name into an order header), always store the entity's ID alongside the denormalized value. This allows updates by ID rather than by name or other mutable attributes.

  • Always add an ID column (e.g., customer_id) to the table that stores denormalized data, not just the display value (e.g., customer_name)
  • Update by ID, not by value — when handling rename/update events, find records to update using the entity ID, never by matching the old string value. Matching by string is fragile: two entities with the same name would both get updated incorrectly
  • If an existing table is missing the ID column, add a migration to include it

Bad — matching by old name:

ruby
when Crm::CustomerRenamed
  old_name = customer.name
  customer.update!(name: event.data.fetch(:name))
  Deal.where(customer_name: old_name).update_all(customer_name: event.data.fetch(:name))

Good — matching by ID:

ruby
when Crm::CustomerRenamed
  Customer.find_by!(customer_id: event.data.fetch(:customer_id)).update!(name: event.data.fetch(:name))
  Deal.where(customer_id: event.data.fetch(:customer_id)).update_all(customer_name: event.data.fetch(:name))
6. Facade methods
  • Only create facade methods that are actually called by controllers or views
  • Do not create speculative facade methods "in case they might be useful"
  • If a facade method is no longer used, remove it
7. Register in lib/configuration.rb

Add the read model to lib/configuration.rb:

For Pattern A:

ruby
def enable_{module_name}_read_model(event_store)
  ModuleName::Configuration.new.call(event_store)
end

For Pattern B:

ruby
def enable_{module_name}_read_model(event_store)
  ModuleName::Configuration.new(event_store).call
end

Call the method from def call(event_store, command_bus).

8. Add to mutation testing

Add the module to the app's .mutant.yml under matcher.subjects:

yaml
matcher:
  subjects:
    - ModuleName*

Add ModuleName::Configuration#call and ModuleName::Rendering::* to matcher.ignore.

9. Run verification

Run in this order:

  1. rails test test/{module_name}/ - unit tests for the new read model
  2. rails test test/integration/ - integration tests still pass
  3. make test - all tests green
  4. RAILS_ENV=test bundle exec mutant run "ModuleName*" - 100% mutation score

Key conventions

  • No comments in code or tests
  • No local variables - prefer method calls
  • No named params unless required
  • No return unless guards — always use find_by!, never find_by with &.
  • Read models must not access other read models — if a read model needs data owned by another (e.g., entity names for activity descriptions), subscribe to the same domain events and maintain an internal lookup table (e.g., EntityName with entity_uid + name). This keeps the read model self-contained. Another approach worth considering is a SummaryEvent — an event built from other events that carries all the necessary data, so the read model handler receives everything it needs in a single event without any lookups.
  • Use private_constant for ActiveRecord classes
  • Facade methods only when used by controllers/views
  • Use uuid type in migrations for UUID columns
  • Single EventHandler class per read model with case event routing — all in configuration.rb
  • Single test file per read model
  • Test event flows must reflect real application flows — include store registration events, customer registration, etc.
  • All calls are synchronous - no async/concurrency concerns
  • 100% mutation score required
  • Test-first TDD - write tests before implementation

© RailsEventStore, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/read-model of RailsEventStore/ecommerce.

Open the folder on GitHubat commit 42c6c23

Compare with similar skills

Read Model next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Read Model compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Read Model this skillRailsEventStore/ecommerce507—~3kAutomated safety check: PassMIT
Replication Driven Researchbrycewang-stanford/Auto-Empirical-Research-Skills4.5k—~1.7kAutomated safety check: PassCustom licence
Web Application Testinganthropics/skills180k51 repos~966Automated safety check: PassApache-2.0
Diagnosing Bugsfossasia/eventyay-interpretation1.6k32 repos~2.1kAutomated safety check: PassApache-2.0
TDDpietheinstrengholt/rssmonster56430 repos~906Automated safety check: PassMIT
MongoDB Source Connector E2E Harnessairbytehq/airbyte22k—~1.9kAutomated safety check: PassCustom licence

Similar skills

  • Replication Driven Research

    brycewang-stanford/Auto-Empirical-Research-Skills

    A skill your agent uses when starting empirical analysis, creating a data pipeline, generating results, or when data or model specifications change.

    4.5k GitHub stars~1.7k tokensUpdated 2 days ago
    Testing & QAAuto-check passed
  • Web Application Testing

    anthropics/skills

    Official

    Tests local web applications with Python Playwright scripts, checking frontend behavior, capturing screenshots and reading browser console logs.

    180k GitHub starsUsed in 51 repos~966 tokens
    Testing & QAAuto-check passed
  • Diagnosing Bugs

    fossasia/eventyay-interpretation

    Diagnosis loop for hard bugs and performance regressions. An agent skill from fossasia/eventyay-interpretation.

    1.6k GitHub starsUsed in 32 repos~2.1k tokens
    Testing & QAAuto-check passed
  • TDD

    pietheinstrengholt/rssmonster

    Test-driven development. An agent skill from pietheinstrengholt/rssmonster.

    564 GitHub starsUsed in 30 repos~906 tokens
    Testing & QAAuto-check passed
  • Official

    Starts a throwaway MongoDB 7.0 replica set and runs the Airbyte spec, check, discover and read commands against source-mongodb-v2 images for local end-to-end testing.

    22k GitHub stars~1.9k tokensUpdated today
    Testing & QAAuto-check passed
  • Official

    Stands up a throwaway local SQL Server 2022 backend, applies SQL fixtures and runs Airbyte spec, check, discover and read against source-mssql images.

    22k GitHub stars~4.4k tokensUpdated today
    Testing & QAAuto-check passed

More from RailsEventStore/ecommerce

All 8 skills in this repo
  • Commit

    RailsEventStore/ecommerce

    Create atomic git commits following project conventions. An agent skill from RailsEventStore/ecommerce.

    507 GitHub stars~767 tokensUpdated 17 days ago
    Auto-check passed
  • Controller

    RailsEventStore/ecommerce

    Create a controller with commands, read model queries, routes, and integration tests

    507 GitHub stars~1.9k tokensUpdated 17 days ago
    Auto-check passed
  • Domain

    RailsEventStore/ecommerce

    Create a new domain bounded context with aggregates, commands, events, and handlers

    507 GitHub stars~2.6k tokensUpdated 17 days ago
    Auto-check passed
  • New App

    RailsEventStore/ecommerce

    Scaffold a new Rails app with event sourcing, following project conventions (todomvc as reference)

    507 GitHub stars~3.1k tokensUpdated 17 days ago
    Auto-check passed
  • New Feature

    RailsEventStore/ecommerce

    Plan a new feature end-to-end — impact analysis across all layers, start-from-the-middle slicing into deployable steps, then delegating to /domain, /read-model, /controller skills

    507 GitHub stars~1.6k tokensUpdated 17 days ago
    Auto-check passed
  • Upgrade Rails

    RailsEventStore/ecommerce

    Upgrade Rails framework to a newer version following the smooth upgrade methodology

    507 GitHub stars~976 tokensUpdated 17 days ago
    Auto-check passed

Questions about Read Model

What does Read Model do?

Build a new read model following project conventions (event handlers, tests, migration, configuration). Read Model is an agent skill from RailsEventStore/ecommerce.

When should I use Read Model?

Read Model fits situations like: testing & QA work in your project.

How do I install Read Model in Claude Code?

Run `npx skills add RailsEventStore/ecommerce --skill read-model -a claude-code`. Or copy the skill folder (.claude/skills/read-model in RailsEventStore/ecommerce) into .claude/skills/read-model in your project. Claude Code loads it when a task matches its description.

How do I install Read Model in Codex?

Run `npx skills add RailsEventStore/ecommerce --skill read-model -a codex`. Or copy the skill folder (.claude/skills/read-model in RailsEventStore/ecommerce) into .agents/skills/read-model in your project. Codex loads it when a task matches its description.

Can I use Read Model in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add RailsEventStore/ecommerce --skill read-model -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/read-model, .gemini/skills/read-model, .github/skills/read-model and .opencode/skills/read-model in your project.

What does Read Model need to run?

Going by SKILL.md and its folder, Read Model needs the command-line tools its instructions call (rails, make and bundle).

Does Read Model access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Read Model safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Read Model use?

Read Model is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Read Model use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Read Model?

Skills that share tags, products or a category with Read Model: Replication Driven Research (brycewang-stanford/Auto-Empirical-Research-Skills, 4.5k stars), Web Application Testing (anthropics/skills, 180k stars), Diagnosing Bugs (fossasia/eventyay-interpretation, 1.6k stars) and TDD (pietheinstrengholt/rssmonster, 564 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Read Model?

RailsEventStore (a GitHub organization) maintains it in RailsEventStore/ecommerce, which has 507 GitHub stars. The repository holds 8 skills in this directory. The repository was last updated on September 21, 2026.

Source: RailsEventStore/ecommerce on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.