Rails Event Store

Custom Repository

Installation with Bundler

By default RailsEventStore will use the ActiveRecord event repository. If you want to use another event repository without loading unnecessary ActiveRecord dependency, you'll need to do:

gem "rails_event_store", require: "rails_event_store/all"
gem "your_custom_repository"

After running bundle install, Rails Event Store should be ready to be used. See custom repository README to learn how to setup its data store.

Configure custom repository

Rails.application.configure do
  config.to_prepare do
    Rails.configuration.event_store = RailsEventStore::Client.new(repository: YourCustomRepository.new)
  end
end

Community event repositories

Those repositories were written by community members and are not guaranteed to be up to date with latest Rails event store.

Writing your own repository

If you want to write your own repository, we provide a suite of tests that you can re-use. Just require and include it in your repository spec. Make sure to meditate on which expected_version option you are going to support and how.

Using RubyEventStore::InMemoryRepository for faster tests

RubyEventStore comes with RubyEventStore::InMemoryRepository that you can use in tests instead of the default one. InMemoryRepository does not persist events but offers the same characteristics as RailsEventStoreActiveRecord::EventRepository. It is tested with the same test suite and raises identical exceptions.

RSpec.configure do |c|
  c.around(:each)
    Rails.configuration.event_store = RailsEventStore::Client.new(
      repository: RubyEventStore::InMemoryRepository.new
    )
    # add subscribers here
  end
end

If you want even faster tests you can additionally skip event's serialization.

RSpec.configure do |c|
  c.around(:each)
    Rails.configuration.event_store = RailsEventStore::Client.new(
      repository: RubyEventStore::InMemoryRepository.new,
      mapper: RubyEventStore::Mappers::NullMapper.new,
    )
    # add subscribers here
  end
end

We don't recommend using InMemoryRepository in production even if you don't need to persist events because the repository keeps all published events in memory. This is acceptable in testing because you can throw the instance away for every test and garbage collector reclaims the memory. In production, your memory would keep growing until you restart the application server.

Using Ruby Object Mapper (ROM) for SQL without ActiveRecord or Rails

RubyEventStore comes with RubyEventStore::ROM::EventRepository that you can use with a SQL database without requiring ActiveRecord or when not using Rails altogether. It is tested with the same test suite as the ActiveRecord implementation and raises identical exceptions.

See Using Ruby Event Store without Rails for information on how to use ROM (and Sequel).

Using PgLinearizedEventRepository for linearized writes

rails_event_store_active_record comes with additional version of repository named RailsEventStoreActiveRecord::PgLinearizedEventRepository. It is almost the same as regular active record repository, but has linearized writes to the database and is only restricted to work in PostgreSQL database (as the name suggests).

There are usecases, where you may want to use event store as a queue. For example, you may want to build some read models on separate server and in order to build them correctly, you need to process the facts in the order they were written. In general case it is not that easy, because SQL databases auto-increment rows in the moment of insertion, not commit. So that allows event numbered 42 be already committed, but event numbered 40 still be somewhere in transaction, not readable from outside world. Therefore, the easiest implementation of such queue: "Remember the last processed event id" would not work in that case.

There are many subtleties in this topic, but one of the simplest solutions is to linearize all writes to event store. That's what RailsEventStoreActiveRecord::PgLinearizedEventRepository is for. Of course by linearizing your writes you lose performance and you make it impossible to scale your application above certain level. As usually, your mileage may vary, but such solution is undoubtedly the simplest and good enough in some usecases.