No release in over 3 years
Builds Rails Event Store AggregateRoot
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Active Record Projection

Persistent Rails Event Store projections built on Active Record models.

Built on the Rails Event Store Aggregate Root.

How it works:

  • a projection is built from a stream of events
    • a projection only has a single stream but a stream can be the source of multiple projections
  • each projection has a polymorphic association to a record
    • this record is your Active Record model that functions as an aggregate root
    • it can update its own state and the state of its children by projecting events
  • an async event handler is automatically subscribed to all events that you project using the on method
    • when an event of each type occurs, the projections subscribed to all streams in which that event is linked are notified
    • unseen events are then read, applied and the record is saved
  • optimistic locking is used to control concurrent updates to the projection/record
---
title: Records and Projections
---
classDiagram
  namespace ActiveRecordProjection{
	  class Projection{
	    String: stream
	    Integer: lock_version
	    UUID: last_event_id
	    Integer: record_id
	    String: record_type
	  }
	 }
    
  class AggregateRoot{
  }
  
  class Child{
  }
    
  namespace ActiveRecord{
    class Base{
    }
  }
  
  namespace RailsEventStore{
    class Stream{
    }
  }
  
  AggregateRoot --|> Base
  Child --|> Base
  AggregateRoot *-- Child
  Projection o-- AggregateRoot : record
  Projection o-- Stream : stream
Loading

Installation

Add to your Gemfile and run bundle.

gem 'active_record_projection'

Create a active_record_projection_projections table in your database:

bundle exec rails generate active_record_projection:install

Adding an Active Record Projection

To make your Active Record model a projection of an aggregate root:

  • include ActiveRecordProjection
  • define a get_stream method to allow a stream to be determined from the model data
  • define how to project each event using the Aggregate Root syntax (define on methods)
class Order < ApplicationRecord
  include ActiveRecordProjection

  on OrderSubmitted.event_type do |event|
    self.state = :submitted
    self.delivery_date = event.data.fetch(:delivery_date)
  end

  on OrderExpired.event_type do |_event|
    self.state = :expired
  end

  private

  def get_stream
    "orders$#{uuid}"
  end
end

You need to add a on method for each event type in a stream, even if you don't need it in order to project.