Skip to content

Commit

Permalink
feat: validate entity and event aggregate_id value and type
Browse files Browse the repository at this point in the history
  • Loading branch information
lukashlavacka authored and desheikh committed Dec 23, 2024
1 parent c13dfc5 commit 85cbf65
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## 1.5.5 - 2024-12-23
- Validate value and type of `aggregate_id` between Event and Entity

## 1.5.4 - 2024-12-05
### Changed
- Rails 8.0 is supported
Expand Down
15 changes: 15 additions & 0 deletions lib/eventsimple/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ module Entity
DEFAULT_IGNORE_PROPS = %w[id lock_version].freeze

def event_driven_by(event_klass, aggregate_id:, filter_attributes: [])
if defined?(event_klass._aggregate_id)
raise ArgumentError, "aggregate_id mismatch event:#{event_klass._aggregate_id} entity:#{aggregate_id}" if aggregate_id != event_klass._aggregate_id

begin
aggregate_column_type_in_event = event_klass.column_for_attribute(:aggregate_id).type
aggregate_column_type_in_entity = column_for_attribute(aggregate_id).type

raise ArgumentError, "column type mismatch - event:#{aggregate_column_type_in_event} entity:#{aggregate_column_type_in_entity}" if aggregate_column_type_in_event != aggregate_column_type_in_entity
rescue ActiveRecord::NoDatabaseError
end
end

has_many :events, class_name: event_klass.name.to_s,
foreign_key: :aggregate_id,
primary_key: aggregate_id,
Expand All @@ -18,6 +30,9 @@ def event_driven_by(event_klass, aggregate_id:, filter_attributes: [])
class_attribute :_filter_attributes
self._filter_attributes = [aggregate_id] | Array.wrap(filter_attributes)

class_attribute :_aggregate_id
self._aggregate_id = aggregate_id

# disable automatic timestamp updates
self.record_timestamps = false

Expand Down
12 changes: 12 additions & 0 deletions lib/eventsimple/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ module Event

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def drives_events_for(aggregate_klass, aggregate_id:, events_namespace: nil)
if defined?(aggregate_klass._aggregate_id)
raise ArgumentError, "aggregate_id mismatch event:#{aggregate_id} entity:#{aggregate_klass._aggregate_id}" if aggregate_id != aggregate_klass._aggregate_id

begin
aggregate_column_type_in_event = aggregate_klass.column_for_attribute(aggregate_klass._aggregate_id).type unless aggregate_klass.attribute_names.blank?
aggregate_column_type_in_entity = column_for_attribute(:aggregate_id).type unless aggregate_klass.attribute_names.blank?

raise ArgumentError, "column type mismatch - event:#{aggregate_column_type_in_event} entity:#{aggregate_column_type_in_entity}" if aggregate_column_type_in_event != aggregate_column_type_in_entity
rescue ActiveRecord::NoDatabaseError
end
end

class_attribute :_events_namespace
self._events_namespace = events_namespace

Expand Down
39 changes: 39 additions & 0 deletions spec/lib/eventsimple/entity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,45 @@ module Eventsimple
)
end

describe '.event_driven_by' do
context 'when aggregate_id value mismatch between entity and event' do
let(:user_class) do
Class.new(ApplicationRecord) do
extend Eventsimple::Entity

event_driven_by UserEvent, aggregate_id: :id
end
end

it 'raises argument error' do
expect { user_class }.to(raise_error(ArgumentError, 'aggregate_id mismatch event:canonical_id entity:id'))
end
end

context 'when aggregate_id column type mismatch between entity and event' do
let(:user_class) do
Class.new(ApplicationRecord) do
def self.name
'User'
end

def self.column_for_attribute(column_name)
return OpenStruct.new(type: :int) if column_name == :canonical_id
super
end

extend Eventsimple::Entity

event_driven_by UserEvent, aggregate_id: :canonical_id
end
end

it 'raises argument error' do
expect { user_class }.to(raise_error(ArgumentError, 'column type mismatch - event:string entity:int'))
end
end
end

describe '#projection_matches_events?' do
it 'returns false if the entity no longer matches state from events' do
expect(user.projection_matches_events?).to be true
Expand Down
39 changes: 39 additions & 0 deletions spec/lib/eventsimple/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,43 @@ def self.uses_transaction?(_method) = true
end
end
end

describe '.event_driven_by' do
context 'when aggregate_id mismatch between entity and event' do
let(:event_class) do
Class.new(ApplicationRecord) do
extend Eventsimple::Event

drives_events_for User, aggregate_id: :id, events_namespace: 'UserComponent::Events'
end
end

it 'raises argument error' do
expect { event_class }.to(raise_error(ArgumentError, 'aggregate_id mismatch event:id entity:canonical_id'))
end
end

context 'when aggregate_id column type mismatch between entity and event' do
let(:event_class) do
Class.new(ApplicationRecord) do
def self.name
'UserEvent'
end

def self.column_for_attribute(column_name)
return OpenStruct.new(type: :int) if column_name == :aggregate_id
super
end

extend Eventsimple::Event

drives_events_for User, aggregate_id: :canonical_id, events_namespace: 'UserComponent::Events'
end
end

it 'raises argument error' do
expect { event_class }.to(raise_error(ArgumentError, 'column type mismatch - event:string entity:int'))
end
end
end
end

0 comments on commit 85cbf65

Please sign in to comment.