A minimal, working Rails app wired up to the EventEngine pipeline. It exists to show the whole path end to end — declare an event, compile it, decide how it is processed, handle it — in a codebase small enough to read in one sitting.
Every gem is installed from RubyGems, exactly as a real app would:
gem "event_engine" # the runtime
gem "event_engine-event_definition" # the authoring DSL
gem "event_engine-subscribers" # a processorbin/setup
bin/rails event_definition:generate # definitions → helper + schema.json
bin/rails event_engine:catalog # schema.json → catalog + rules file
bin/rails event_engine:rules:check # every event routed to a registered processor?
bin/rails testThen emit an event:
bin/rails runner 'MarketingEvents.lead_created(lead: Lead.create!(email: "ada@example.com", name: "Ada"))'and watch log/development.log:
[welcome] emailing ada@example.com
[analytics] lead 1 from nil
Everything else is stock Rails.
class LeadCreated < EventEngine::EventDefinition
event_name :lead_created
event_type :domain
domain :marketing
subject :lead
input :lead
required_payload :lead_id, from: :lead, attr: :id
required_payload :email, from: :lead, attr: :email
optional_payload :source, from: :lead, attr: :source
endRead each payload line as "the event carries lead_id, and its value comes from
lead.id." You hand the helper the whole lead; the contract decides what is
captured off it.
load "tasks/event_definition.rake"
EventEngine::Definition.configure do |config|
config.definitions_path = "app/event_definitions"
config.helper_path = "lib/generated/marketing_events.rb"
config.root_module = "MarketingEvents"
config.subject_registry = EventEngine::SubjectRegistry.define { subject :lead }
endbin/rails event_definition:generate compiles the definitions into
lib/generated/marketing_events.rb (a typed method per event) and schema.json
(the committed contract). Both are committed — they are build output, not
runtime work.
events:
lead_created: inline # run subscribers synchronously, during emit
lead_converted: background # enqueue a job, return immediatelybin/rails event_engine:catalog writes this file, listing every catalogued event so
a new one shows up needing a decision. Rules you have already made are preserved.
This is the only place processing is declared. The schema says what an event carries; the rules say what happens to it.
class SendWelcomeEmail < EventEngine::Subscribers::Base
subscribes_to :lead_created
def handle(event)
Rails.logger.info("[welcome] emailing #{event.payload[:email]}")
end
endTwo subscribers listen to lead_created here (SendWelcomeEmail and
TrackLeadSignup) to show fan-out; both run.
require "event_engine"
require "event_engine/definition"
require Rails.root.join("lib/generated/marketing_events")That is the entire host wiring. No publisher to configure, no processors to
register. The runtime registers its publisher at boot, and
event_engine-subscribers registers itself as the :inline and :background
processors. The only explicit require is the generated helper, because it lives
outside the autoload paths.
Keep the generated helper out of the autoload paths. Rails 7.1+ autoloads lib,
so Zeitwerk would expect lib/generated/marketing_events.rb to define
Generated::MarketingEvents and raise on eager load — meaning the app boots in
development and fails in production. See config/application.rb:
config.autoload_lib(ignore: %w[assets tasks generated])Run the rules check in CI. A rule naming a processor nothing registered, or a catalogued event nobody routed, otherwise surfaces only when that event is emitted:
$ bin/rails event_engine:rules:check
EventEngine rules OK: :inline, :background
app/event_definitions/*.rb
│ event_definition:generate
▼
lib/generated/marketing_events.rb + schema.json (committed)
│ event_engine:catalog
▼
db/event_schema.json + config/event_rules.yml (committed)
│
│ MarketingEvents.lead_created(lead: lead)
▼
EventEngine.emit → builds the payload from the contract
│ looks up the rule: lead_created → inline
▼
EventEngine::Subscribers::Processor → your subscribers
| Gem | Role |
|---|---|
event_engine-event_definition |
Declare events, compile the contract. No Rails. |
event_engine |
Build a validated event from the catalog, route it to a processor. |
event_engine-subscribers |
A processor: runs your in-app subscribers, inline or in the background. |
Available as open source under the terms of the MIT License.