Rails Event Store

Using Ruby Event Store without Rails

ActiveRecord and ROM (Sequel) are supported SQL adapters out-of-the-box.

Installation

Add to your Gemfile:

source "https://rubygems.org"

gem "ruby_event_store"

# For ActiveRecord:
gem "activerecord"
gem "rails_event_store_active_record"

# For ROM/Sequel:
gem "rom-sql"
gem "ruby_event_store-rom", require: "ruby_event_store/rom/sql"

# And one of:
gem "sqlite3"
gem "pg"
gem "mysql2"

Creating tables

ActiveRecord: As you are not using rails and its generators, please create required database tables which are equivalent to what our migration would do.

ROM/Sequel migrations

SQL schema migrations can be copied to your project using Rake tasks. (The ROM migrations use Sequel under the hood.)

Add the tasks to your Rakefile to import them into your project:

# In your project Rakefile
require "ruby_event_store/rom/adapters/sql/rake_task"

Then run Rake tasks to get your database setup:

# Copies the migrations to your project (in db/migrate)s
bundle exec rake db:migrations:copy DATABASE_URL=postgres://localhost/database
# <= migration file created db/migrate/20180417201709_create_ruby_event_store_tables.rb

# Run the migrations in your project (in db/migrate)
bundle exec rake db:migrate DATABASE_URL=postgres://localhost/database
# <= db:migrate executed

By default, data and metadata are stored in text columns. You can specify the DATA_TYPE environment variable when copying migrations to use a JSON or JSONB column in Postgres.

bundle exec rake db:migrations:copy DATABASE_URL=postgres://localhost/database DATA_TYPE=jsonb

You can run bundle exec rake -T to get a list of all available tasks. You can also programmatically run migrations (see examples above).

NOTE: Make sure the database connection in your app doesn't try to connect and setup RES before the migrations have run.

Usage

ActiveRecord

require "active_record"
require "rails_event_store_active_record"
require "ruby_event_store"

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])

class OrderPlaced < RubyEventStore::Event
end

event_store = RubyEventStore::Client.new(repository: RailsEventStoreActiveRecord::EventRepository.new)

event_store.publish(
  OrderPlaced.new(data: { order_id: 1, customer_id: 47_271, amount: BigDecimal.new("20.00") }),
  stream_name: "Order-1",
)

ROM/Sequel setup

You simply need to configure your ROM container and then store it globally on RubyEventStore::ROM.env or pass it to the repository constructor.

require "ruby_event_store/rom/sql"

# Use the `setup` helper to configure repositories and mappers.
# Then store an Env instance to get access to the ROM container.
RubyEventStore::ROM.env = RubyEventStore::ROM.setup(:sql, ENV["DATABASE_URL"])

# Use the repository the same as with ActiveRecord
client = RubyEventStore::Client.new(repository: RubyEventStore::ROM::EventRepository.new)

Advanced setup

You can use a specific ROM container per repository to customize it more extensively. This example illustrates how to get at the ROM configuration and even run the latest migrations.

require "ruby_event_store/rom/sql"

config = ROM::Configuration.new(:sql, ENV["DATABASE_URL"])

# Run migrations if you need to (optional)
config.default.run_migrations

# Use the `setup` helper to configure the ROM container
env = RubyEventStore::ROM.setup(config)

# Use the repository the same as with ActiveRecord
client = RubyEventStore::Client.new(repository: RubyEventStore::ROM::EventRepository.new(rom: env))

# P.S. Access the ROM container
container = env.container

This advanced option provides flexibility if you are using a separate database for RES or have other needs that require more granular configurations.

Unavailable features

rails_event_store provides some features that ruby_event_store by design cannot:

  • async handlers and ActiveJob integration

You can implement and provide your own dispatcher which knows how to recognize and enqueue async handlers. Pass it as a dependency to RubyEventStore::Client constructor.

  • Request metadata such as remote_ip and request_id won't be automatically filled in events' metadata.