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 committed Nov 27, 2024
1 parent 1271add commit e14e687
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## 1.5.4 - 2024-09-09
### Changed
- Validate value and type of `aggregate_id` between Event and Entity

## 1.5.3 - 2024-09-09
### Changed
- Pass self to `enable_writes!` block
Expand Down
14 changes: 8 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
eventsimple (1.5.3)
eventsimple (1.5.4)
concurrent-ruby (>= 1.2.3)
dry-struct (~> 1.6)
dry-types (~> 1.7)
Expand Down Expand Up @@ -102,12 +102,13 @@ GEM
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crass (1.0.6)
date (3.3.4)
date (3.4.0)
diff-lcs (1.5.1)
drb (2.2.0)
ruby2_keywords
dry-core (1.0.1)
dry-core (1.0.2)
concurrent-ruby (~> 1.0)
logger
zeitwerk (~> 2.6)
dry-inflector (1.1.0)
dry-logic (1.5.0)
Expand Down Expand Up @@ -169,6 +170,7 @@ GEM
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.1)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand All @@ -184,7 +186,7 @@ GEM
minitest (5.22.2)
mutex_m (0.2.0)
nenv (0.3.0)
net-imap (0.4.16)
net-imap (0.5.1)
date
net-protocol
net-pop (0.1.2)
Expand All @@ -207,7 +209,7 @@ GEM
parser (3.3.0.5)
ast (~> 2.4.1)
racc
pg (1.5.8)
pg (1.5.9)
polyglot (0.3.5)
pry (0.14.2)
coderay (~> 1.1)
Expand Down Expand Up @@ -342,7 +344,7 @@ GEM
stringio (3.1.0)
strscan (3.1.0)
thor (1.3.1)
timeout (0.4.1)
timeout (0.4.2)
treetop (1.6.12)
polyglot (~> 0.3)
tzinfo (2.0.6)
Expand Down
12 changes: 12 additions & 0 deletions lib/eventsimple/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ 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

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
end

has_many :events, class_name: event_klass.name.to_s,
foreign_key: :aggregate_id,
primary_key: aggregate_id,
Expand All @@ -18,6 +27,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
9 changes: 9 additions & 0 deletions lib/eventsimple/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ 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

aggregate_column_type_in_event = aggregate_klass.column_for_attribute(aggregate_klass._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
end

class_attribute :_events_namespace
self._events_namespace = events_namespace

Expand Down
2 changes: 1 addition & 1 deletion lib/eventsimple/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Eventsimple
VERSION = '1.5.3'
VERSION = '1.5.4'
end
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 e14e687

Please sign in to comment.