用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/RailsEventStore/ecommerce --skill new-app命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Create atomic git commits following project conventions
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
Build a new read model following project conventions (event handlers, tests, migration, configuration)
基于 SOC 职业分类
正在显示 SKILL.md
| name | new-app |
| description | Scaffold a new Rails app with event sourcing, following project conventions (todomvc as reference) |
Use this skill when asked to create a new Rails application in the apps/ directory. Each app is a standalone Rails application that uses domain modules from domains/ and the shared infra gem.
apps/todo_mvc is the canonical reference for app structure (initializer, Configuration, test harness, Makefile). For the current RES 3.0 wiring and gem versions, use apps/rails_application and apps/twitter — todo_mvc still pins RES 2.x, which is stale. When the two disagree, follow rails_application.
Always scaffold on the newest Ruby and newest Rails — do not hardcode versions from this doc, they go stale.
gem list rails --remote --exact | head -1 (checks RubyGems, not just what's installed locally). As of this writing: Rails 8.1.3.~/.rbenv/versions/ (ls ~/.rbenv/versions | sort -V | tail). As of this writing: Ruby 4.0.1. Set it in the app's .ruby-version. Ruby 4.0.1 works with Rails 8.1 + RES 3.0 + the infra/domain gems.~/.rbenv/versions/{ruby}/bin/rails -v.Invoke rails/bundle via the full rbenv path for the chosen Ruby, e.g. ~/.rbenv/versions/4.0.1/bin/rails, ~/.rbenv/versions/4.0.1/bin/bundle — the rails shell alias may resolve to a different Ruby.
Commit frequently at natural checkpoints, not once at the end. Commit directly on the current branch — do not create a feature branch. Suggested commits: (1) after generating the app + configuring the Gemfile, (2) after RES wiring is green, (3) after each domain/read model. Per project convention, use the /commit skill and never mention Claude in commit messages.
Before writing any code, clarify:
crm, inventory_tracker)domains/ or new ones to be created)Run from the apps/ directory, using the full rbenv path for the newest Ruby (see Version policy above):
cd apps && ~/.rbenv/versions/{ruby}/bin/rails new {app_name} --database=postgresql --css=tailwind --skip-test
--skip-test (not --skip-test-unit, which no longer exists) drops the default test setup so we can install our own minitest + mutant harness.
Important post-generation steps:
echo "{ruby}" > apps/{app_name}/.ruby-version (the generator pins the global default, which may not be the newest).rails new creates a nested .git directory inside the new app. It must be removed so the app is part of the parent repo. rm -rf is blocked by the repo's git-safety hook, so ask the user to run it manually: rm -rf apps/{app_name}/.git. Do not proceed to committing until it's gone.Add these gems to the generated Gemfile (after jbuilder, before the tzinfo/solid gems). Use RES 3.0 to match rails_application:
gem "rails_event_store", ">= 3.0", "< 4.0"
gem "arkency-command_bus"
gem "infra", path: "../../infra"
Rails 8.1 with --skip-test does not generate a capybara/selenium test group. Just add a test group:
group :test do
gem "mutant-minitest"
end
Run bundle install (via the full rbenv path). Commit the generated app + Gemfile now (checkpoint 1).
Create config/initializers/rails_event_store.rb:
require "rails_event_store"
require "arkency/command_bus"
require_relative "../../lib/configuration"
Rails.configuration.to_prepare do
Rails.configuration.event_store = Infra::EventStore.main
Rails.configuration.command_bus = Arkency::CommandBus.new
Configuration.new.call(Rails.configuration.event_store, Rails.configuration.command_bus)
end
Create lib/configuration.rb. For the initial scaffold there is no domain yet, so call only wires event linking — this keeps the app bootable and tests green before any domain exists:
require_relative "../../../infra/lib/infra"
class Configuration
def call(event_store, command_bus)
enable_res_infra_event_linking(event_store)
end
private
def enable_res_infra_event_linking(event_store)
[
RailsEventStore::LinkByEventType.new,
RailsEventStore::LinkByCorrelationId.new,
RailsEventStore::LinkByCausationId.new
].each { |h| event_store.subscribe_to_all_events(h) }
end
end
As domains and read models are added later, extend call: require_relative the domain, add {DomainModule}::Configuration.new.call(event_store, command_bus), and give each read model its own enable_* private method called from call. Do not add a require_relative to a domain that doesn't exist yet — it will break boot.
In RES 3.0 the generator was renamed — the old rails_event_store_active_record:migration no longer exists. Use the ruby_event_store namespace with a data type (jsonb pairs cleanly with infra's RailsEventStore::JSONClient):
cd apps/{app_name} && ~/.rbenv/versions/{ruby}/bin/bundle exec rails generate ruby_event_store:active_record:migration --data-type=jsonb
Then rails db:create && rails db:migrate (via the full rbenv path). The generated migration creates event_store_events and event_store_events_in_streams with uuid event_id and jsonb data/metadata.
Ensure app/controllers/application_controller.rb has:
class ApplicationController < ActionController::Base
allow_browser versions: :modern
def command_bus
Rails.configuration.command_bus
end
def event_store
Rails.configuration.event_store
end
end
Replace the generated test/test_helper.rb (which has parallelize and fixtures :all):
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require "mutant/minitest/coverage"
ActiveJob::Base.logger = Logger.new(nil)
class InMemoryRESTestCase < ActiveSupport::TestCase
def before_setup
result = super
@previous_event_store = Rails.configuration.event_store
@previous_command_bus = Rails.configuration.command_bus
Rails.configuration.event_store = Infra::EventStore.in_memory
Rails.configuration.command_bus = Arkency::CommandBus.new
Configuration.new.call(
Rails.configuration.event_store, Rails.configuration.command_bus
)
result
end
def before_teardown
result = super
Rails.configuration.event_store = @previous_event_store
Rails.configuration.command_bus = @previous_command_bus
result
end
def event_store
.configuration.event_store
.configuration.command_bus
<
result =
= .configuration.event_store
= .configuration.command_bus
.configuration.event_store = .in_memory_rails
.configuration.command_bus = .new
.new.call(.configuration.event_store, .configuration.command_bus)
result
result =
.configuration.event_store =
.configuration.command_bus =
result
.configuration.command_bus
Create .mutant.yml in the app root. On the initial scaffold there are no subjects yet, so start with empty lists (mutant isn't run until there's domain code to cover):
includes:
- test
requires:
- ./config/environment
integration: minitest
usage: opensource
coverage_criteria:
timeout: true
process_abort: true
matcher:
subjects: []
ignore: []
As each read model is added, append its namespace to subjects (e.g. - Tweets*) and its AR model + Configuration#call to ignore. See apps/crm/.mutant.yml for a fully-populated example.
Add targets to the root Makefile:
install-{app_name}:
@make -C apps/{app_name} install
test-{app_name}:
@make -C apps/{app_name} test
mutate-{app_name}:
@make -C apps/{app_name} mutate
Add install-{app_name} to the install: target, test-{app_name} to test:, and mutate-{app_name} to mutate:.
Create Makefile in the app directory:
install:
@bin/setup
dev:
@make -j 2 web css
test:
@bin/rails test
mutate:
@RAILS_ENV=test bundle exec mutant run
tailwind:
@bin/rails tailwindcss:build
css:
@bin/rails tailwindcss:watch
web:
@bin/rails server -p {port}
.PHONY: install dev test mutate tailwind css web
Use a unique port per app (3000 for rails_application, 3001+ for new apps).
Controllers dispatch commands and query read models:
class ResourceController < ApplicationController
def index
@records = ReadModelName.all
end
def create
id = SecureRandom.uuid
ActiveRecord::Base.transaction do
command_bus.call(DomainModule::CreateCommand.new(id: id, ...))
end
redirect_to root_path
end
def update
command_bus.call(DomainModule::UpdateCommand.new(id: params[:id], ...))
redirect_to root_path
end
def destroy
command_bus.call(DomainModule::DeleteCommand.new(id: params[:id]))
redirect_to root_path
end
end
Build the app incrementally, with tests passing at each step:
/domain skill)/read-model skill)make test and mutation testingrails/bundle alias: The shell alias may resolve to the wrong Ruby. Always use the full rbenv path: ~/.rbenv/versions/{ruby}/bin/rails, ~/.rbenv/versions/{ruby}/bin/bundle..git: rails new creates its own git repo. It must be removed before the app can be committed to the parent repo. rm -rf is blocked by the git-safety hook — ask the user to run rm -rf apps/{app_name}/.git manually..ruby-version: The generator pins the rbenv global default, not necessarily the newest — overwrite it with the chosen Ruby.ruby_event_store:active_record:migration, not the old rails_event_store_active_record:migration (see step 6).bin/rails commands (migrations, generators, tests): Must run from apps/{app_name}/ directory, not project root.apps/require_relative "../../../domains/{name}/lib/{name}"gem "infra", path: "../../infra"app/read_models/{name}/configuration.rbevent.data.fetch(:key)command_bus.call(...) for writes, read models for queriesSecureRandom.uuid)