rails_simple_event_sourcing 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 686785bc4c8f52ae548b2880cb664b57d5a048925cda0f4cfe0d2962acbfdb3e
4
- data.tar.gz: 9555f13015e4aaacdfe8ce8b4866ea8d5f6e1a19d0d4f0766b1d4b9c5b36a64e
3
+ metadata.gz: be05ef0dea303db4860a86e10e0ed0d62b5e03de00e07ee80ad374f95e20cc1e
4
+ data.tar.gz: 3eaaf851f72b6a84358e7908a55c2ddc437217467786fac57d658edcc4c70b30
5
5
  SHA512:
6
- metadata.gz: ba9de2c72e6ff9c242da76379ae476aa1139bba37b58eb60dced8bd6c37de83dc3335e9d2f031a6ccf3930e7211f1bafc96c65647550a020e7dcd71e262207d2
7
- data.tar.gz: 691d056df008df794ae8b4833295185eb0ee6f568b2f28b19d2ddbea79bec4069b64143a1c4723c685f49030be97c1e0f964d53ae451551efd1fc6813f4f7c39
6
+ metadata.gz: e19d692f980963be17d5adb2a55678a57be33fb65b04046215d44a1bd48181abfebcc2f3ea8d8cfaf4946570bcf8fb25b0abbc48e28fe7b6a85d253f4b51ca71
7
+ data.tar.gz: 65ad82bb88d8feae84ea47514debdd95532144830e07d65b36c4482f07ad095de5d374e2e6fb77dcace25984a885b8cc63b79fc729a3854f041b95129e4b923b
data/README.md CHANGED
@@ -213,16 +213,47 @@ Events represent **facts** - things that have already happened in your system. T
213
213
  - Store immutable data about state changes
214
214
  - Use past tense naming (e.g., `CustomerCreated`, not `CreateCustomer`)
215
215
  - Define which aggregate (model) they apply to
216
- - Specify how to apply themselves to the aggregate
217
216
  - Are stored permanently in the event log
218
217
 
219
218
  **Key Concepts:**
220
219
  - `aggregate_class` - The model this event applies to (optional - some events may not modify models)
221
220
  - `event_attributes` - Fields stored in the event payload
222
- - `apply(aggregate)` - Method that applies the event to an aggregate instance
221
+ - `apply(aggregate)` - **Optional** method that applies the event to an aggregate instance
223
222
  - `aggregate_id` - Links the event to a specific aggregate instance
224
223
 
225
- **Example - Create Event:**
224
+ **Example - Basic Event (Automatic Application):**
225
+
226
+ ```ruby
227
+ class Customer
228
+ module Events
229
+ class CustomerCreated < RailsSimpleEventSourcing::Event
230
+ aggregate_class Customer
231
+ event_attributes :first_name, :last_name, :email, :created_at, :updated_at
232
+ end
233
+ end
234
+ end
235
+ ```
236
+
237
+ **Automatic Attribute Application:**
238
+
239
+ By default, all attributes declared in `event_attributes` will be **automatically applied** to the aggregate. You don't need to implement the `apply` method unless you have custom logic requirements.
240
+
241
+ The default implementation sets each event attribute on the aggregate:
242
+ ```ruby
243
+ # This happens automatically
244
+ aggregate.first_name = first_name
245
+ aggregate.last_name = last_name
246
+ aggregate.email = email
247
+ # ... and so on for all event_attributes
248
+ ```
249
+
250
+ **Example - Custom Apply Method (When Needed):**
251
+
252
+ You may still need to implement a custom `apply` method in certain cases:
253
+ - Setting computed or derived values
254
+ - Complex business logic during application
255
+ - Handling nested objects or special data transformations
256
+ - Setting the aggregate ID explicitly (though this is usually handled automatically)
226
257
 
227
258
  ```ruby
228
259
  class Customer
@@ -232,12 +263,13 @@ class Customer
232
263
  event_attributes :first_name, :last_name, :email, :created_at, :updated_at
233
264
 
234
265
  def apply(aggregate)
266
+ # Custom logic example
235
267
  aggregate.id = aggregate_id
236
- aggregate.first_name = first_name
237
- aggregate.last_name = last_name
238
- aggregate.email = email
239
- aggregate.created_at = created_at
240
- aggregate.updated_at = updated_at
268
+ aggregate.full_name = "#{first_name} #{last_name}" # Computed value
269
+ aggregate.email_normalized = email.downcase.strip # Transformation
270
+
271
+ # You can still call super to apply remaining attributes automatically
272
+ super
241
273
  end
242
274
  end
243
275
  end
@@ -246,8 +278,8 @@ end
246
278
 
247
279
  **Understanding the Event Structure:**
248
280
  - `aggregate_class Customer` - Specifies which model this event modifies
249
- - `event_attributes` - Defines what data gets stored in the event's JSON payload
250
- - `apply(aggregate)` - Receives an aggregate instance and applies the event's changes to it
281
+ - `event_attributes` - Defines what data gets stored in the event's JSON payload and what will be automatically applied
282
+ - `apply(aggregate)` - Optional method; only implement if you need custom logic beyond automatic attribute assignment
251
283
  - `aggregate_id` - Auto-generated for creates, must be provided for updates/deletes
252
284
 
253
285
  **Note on aggregate_class:**
@@ -487,9 +519,7 @@ class Customer::Events::CustomerDeleted < RailsSimpleEventSourcing::Event
487
519
  aggregate_class Customer
488
520
  event_attributes :deleted_at
489
521
 
490
- def apply(aggregate)
491
- aggregate.deleted_at = deleted_at
492
- end
522
+ # No need to implement apply - deleted_at will be automatically set on the aggregate
493
523
  end
494
524
  ```
495
525
 
@@ -10,14 +10,21 @@ module RailsSimpleEventSourcing
10
10
  belongs_to :eventable, polymorphic: true, optional: true
11
11
  alias aggregate eventable
12
12
 
13
+ # Validations
14
+ validates :version, presence: true, numericality: { only_integer: true, greater_than: 0 }
15
+ validates :version, uniqueness: { scope: :aggregate_id }, if: -> { aggregate_id.present? }
16
+
13
17
  # Callbacks for automatic aggregate lifecycle
14
18
  before_validation :setup_event_fields, on: :create
19
+ before_validation :set_version
15
20
  before_validation :apply_event_to_aggregate, on: :create, if: :aggregate_defined?
16
21
  before_save :persist_aggregate, if: :aggregate_defined?
17
22
 
18
- # Must be implemented by subclasses
19
- def apply(_aggregate)
20
- raise NotImplementedError, "#{self.class}#apply must be implemented"
23
+ def apply(aggregate)
24
+ payload.each do |key, value|
25
+ aggregate.send("#{key}=", value) if aggregate.respond_to?("#{key}=")
26
+ end
27
+ aggregate
21
28
  end
22
29
 
23
30
  private
@@ -28,6 +35,17 @@ module RailsSimpleEventSourcing
28
35
  self.metadata = CurrentRequest.metadata&.compact&.presence
29
36
  end
30
37
 
38
+ def set_version
39
+ self.version ||= calculate_next_version
40
+ end
41
+
42
+ def calculate_next_version
43
+ return 1 unless aggregate_id
44
+
45
+ max_version = Event.where(aggregate_id:).maximum(:version) || 0
46
+ max_version + 1
47
+ end
48
+
31
49
  def apply_event_to_aggregate
32
50
  @aggregate_for_persistence = aggregate_repository.find_or_build(aggregate_id)
33
51
  self.eventable = @aggregate_for_persistence
@@ -7,6 +7,7 @@ class CreateRailsSimpleEventSourcingEvents < ActiveRecord::Migration[7.1]
7
7
  t.string :type, null: false
8
8
  t.string :event_type, null: false
9
9
  t.string :aggregate_id
10
+ t.integer :version, null: false
10
11
  t.jsonb :payload
11
12
  t.jsonb :metadata
12
13
 
@@ -15,6 +16,7 @@ class CreateRailsSimpleEventSourcingEvents < ActiveRecord::Migration[7.1]
15
16
  t.index :type
16
17
  t.index :event_type
17
18
  t.index :aggregate_id
19
+ t.index %i[aggregate_id version], unique: true, name: 'index_events_on_aggregate_id_and_version'
18
20
  t.index :payload, using: :gin
19
21
  t.index :metadata, using: :gin
20
22
  end
@@ -14,7 +14,7 @@ module RailsSimpleEventSourcing
14
14
  private
15
15
 
16
16
  def load_event_stream
17
- @aggregate.events.order(created_at: :asc)
17
+ @aggregate.events.order(version: :asc)
18
18
  end
19
19
 
20
20
  def apply_events(events)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSimpleEventSourcing
4
- VERSION = '1.0.1'
4
+ VERSION = '1.0.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_simple_event_sourcing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damian Baćkowski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-11 00:00:00.000000000 Z
11
+ date: 2026-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg