rails_simple_event_sourcing 1.0.3 → 1.0.4

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: be05ef0dea303db4860a86e10e0ed0d62b5e03de00e07ee80ad374f95e20cc1e
4
- data.tar.gz: 3eaaf851f72b6a84358e7908a55c2ddc437217467786fac57d658edcc4c70b30
3
+ metadata.gz: 8bae2bfe7377ee1c5b4db35484463b437c75c8c9822791aef21867d749abd3fc
4
+ data.tar.gz: b025d6954826b5a1583d0531da3e816386e407c0b26803e903b1c700ecaab2a1
5
5
  SHA512:
6
- metadata.gz: e19d692f980963be17d5adb2a55678a57be33fb65b04046215d44a1bd48181abfebcc2f3ea8d8cfaf4946570bcf8fb25b0abbc48e28fe7b6a85d253f4b51ca71
7
- data.tar.gz: 65ad82bb88d8feae84ea47514debdd95532144830e07d65b36c4482f07ad095de5d374e2e6fb77dcace25984a885b8cc63b79fc729a3854f041b95129e4b923b
6
+ metadata.gz: d3f40d21d78f8d5a978086439521814a9f3903eda901669b88cafc070ab8c01fd7664cb9e5cfefa35c401384c780bc867137e9486e3d3ee37d58ae4ffa3938f6
7
+ data.tar.gz: 3aa5edd3c94f4fe350c5409371430e7e242bdfef76e472bd9f394d011b69b97b295438c3161f2d70c38e7cdb1ca42b4d1f59b335994f175cdbd76f86ebd8b0e1
data/README.md CHANGED
@@ -10,11 +10,13 @@ If you need a more comprehensive solution, check out:
10
10
  - [Features](#features)
11
11
  - [Requirements](#requirements)
12
12
  - [Installation](#installation)
13
+ - [Configuration](#configuration)
13
14
  - [Usage](#usage)
14
15
  - [Directory Structure](#directory-structure)
15
16
  - [Commands](#commands)
16
17
  - [Command Handlers](#command-handlers)
17
18
  - [Events](#events)
19
+ - [Registering Command Handlers](#registering-command-handlers)
18
20
  - [Controller Integration](#controller-integration)
19
21
  - [Update and Delete Operations](#update-and-delete-operations)
20
22
  - [Metadata Tracking](#metadata-tracking)
@@ -22,6 +24,8 @@ If you need a more comprehensive solution, check out:
22
24
  - [Testing](#testing)
23
25
  - [Limitations](#limitations)
24
26
  - [Troubleshooting](#troubleshooting)
27
+ - [Command Handler Registry](#command-handler-registry)
28
+ - [CommandHandlerNotFoundError](#commandhandlernotfounderror)
25
29
  - [Contributing](#contributing)
26
30
  - [License](#license)
27
31
 
@@ -31,6 +35,7 @@ If you need a more comprehensive solution, check out:
31
35
  - **Automatic Aggregate Reconstruction** - Rebuild model state by replaying events
32
36
  - **Built-in Metadata Tracking** - Captures request context (IP, user agent, params, etc.)
33
37
  - **Read-only Model Protection** - Prevents accidental direct model modifications
38
+ - **Command Handler Registry** - Explicit command-to-handler mapping with fallback to convention
34
39
  - **Simple Command Pattern** - Clear command → handler → event flow
35
40
  - **PostgreSQL JSONB Storage** - Efficient JSON storage for event payloads and metadata
36
41
  - **Minimal Configuration** - Convention over configuration approach
@@ -71,6 +76,19 @@ rake db:migrate
71
76
 
72
77
  This creates the `rails_simple_event_sourcing_events` table that stores your event log.
73
78
 
79
+ ## Configuration
80
+
81
+ You can configure the behavior of the gem using the configuration block:
82
+
83
+ ```ruby
84
+ # config/initializers/rails_simple_event_sourcing.rb
85
+ RailsSimpleEventSourcing.configure do |config|
86
+ # When true, falls back to convention-based handler resolution
87
+ # When false, requires explicit registration of all handlers
88
+ config.use_naming_convention_fallback = true
89
+ end
90
+ ```
91
+
74
92
  ## Usage
75
93
 
76
94
  ### Architecture Overview
@@ -144,6 +162,11 @@ Command handlers contain the **business logic** for executing commands. They:
144
162
  - Handle errors gracefully
145
163
  - Return a `RailsSimpleEventSourcing::Result` object
146
164
 
165
+ **Handler Discovery:**
166
+ Handlers can be discovered in two ways:
167
+ 1. **Explicit Registration** (recommended) - Using the `CommandHandlerRegistry` to register handlers
168
+ 2. **Convention-based** - Using naming convention mapping (can be disabled via configuration)
169
+
147
170
  **Result Object:**
148
171
  The `Result` struct has three fields:
149
172
  - `success?` - Boolean indicating if the operation succeeded
@@ -286,6 +309,38 @@ end
286
309
  - Optional - you can have events without an aggregate (e.g., `UserLoginFailed` for logging only)
287
310
  - The corresponding model should include `RailsSimpleEventSourcing::Events` for read-only protection
288
311
 
312
+ ### Registering Command Handlers
313
+
314
+ The recommended approach is to register command handlers explicitly using the registry. This makes the command-to-handler mapping explicit and avoids relying on naming conventions.
315
+
316
+ ```ruby
317
+ # config/initializers/rails_simple_event_sourcing.rb
318
+
319
+ RailsSimpleEventSourcing.configure do |config|
320
+ config.use_naming_convention_fallback = false
321
+ end
322
+
323
+ Rails.application.config.after_initialize do
324
+ # Register all command handlers
325
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
326
+ Customer::Commands::Create,
327
+ Customer::CommandHandlers::Create
328
+ )
329
+
330
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
331
+ Customer::Commands::Update,
332
+ Customer::CommandHandlers::Update
333
+ )
334
+
335
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
336
+ Customer::Commands::Delete,
337
+ Customer::CommandHandlers::Delete
338
+ )
339
+ end
340
+ ```
341
+
342
+ If you prefer the convention-based approach, you don't need to take any action, as the use_naming_convention_fallback is set to true by default.
343
+
289
344
  ### Controller Integration
290
345
 
291
346
  Here's how to wire everything together in a controller:
@@ -525,6 +580,35 @@ end
525
580
 
526
581
  ## Testing
527
582
 
583
+ ### Setting Up Tests with Command Handler Registry
584
+
585
+ If you're using the command handler registry with `use_naming_convention_fallback = false`, you'll need to register your command handlers in your tests. There are a few approaches to handling this:
586
+
587
+ 1. **Create an initializer in the test app:**
588
+
589
+ ```ruby
590
+ # test/dummy/config/initializers/rails_simple_event_sourcing.rb
591
+ Rails.application.config.after_initialize do
592
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
593
+ Customer::Commands::Create,
594
+ Customer::CommandHandlers::Create
595
+ )
596
+ # Register other handlers...
597
+ end
598
+ ```
599
+
600
+ 2. **Register in test_helper.rb:**
601
+
602
+ ```ruby
603
+ # test/test_helper.rb
604
+ # After requiring the environment
605
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
606
+ Customer::Commands::Create,
607
+ Customer::CommandHandlers::Create
608
+ )
609
+ # Register other handlers...
610
+ ```
611
+
528
612
  ### Testing Commands
529
613
 
530
614
  ```ruby
@@ -630,15 +714,51 @@ Be aware of these limitations when using this gem:
630
714
 
631
715
  ## Troubleshooting
632
716
 
717
+ ### Command Handler Registry
718
+
719
+ The gem provides a registry pattern for explicitly mapping commands to their handlers. This is a more robust alternative to the convention-based mapping.
720
+
721
+ **Configuration:**
722
+
723
+ ```ruby
724
+ # config/initializers/rails_simple_event_sourcing.rb
725
+ RailsSimpleEventSourcing.configure do |config|
726
+ # Set to false to disable convention-based mapping
727
+ config.use_naming_convention_fallback = false
728
+ end
729
+
730
+ # Register command handlers
731
+ Rails.application.config.after_initialize do
732
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
733
+ Customer::Commands::Create,
734
+ Customer::CommandHandlers::Create
735
+ )
736
+
737
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
738
+ Customer::Commands::Update,
739
+ Customer::CommandHandlers::Update
740
+ )
741
+
742
+ RailsSimpleEventSourcing::CommandHandlerRegistry.register(
743
+ Customer::Commands::Delete,
744
+ Customer::CommandHandlers::Delete
745
+ )
746
+ end
747
+ ```
748
+
633
749
  ### CommandHandlerNotFoundError
634
750
 
635
- **Error:** `RailsSimpleEventSourcing::CommandHandler::CommandHandlerNotFoundError: Handler Customer::CommandHandlers::Create not found`
751
+ **Error:** `RailsSimpleEventSourcing::CommandHandler::CommandHandlerNotFoundError: Handler Customer::CommandHandlers::Create not found` or `No handler registered for Customer::Commands::Create`
636
752
 
637
- **Cause:** The command handler class doesn't follow the naming convention.
753
+ **Causes:**
754
+ 1. The command handler class doesn't follow the naming convention (when using convention-based mapping)
755
+ 2. The command handler hasn't been registered with the registry (when using explicit registration)
638
756
 
639
- **Solution:** Ensure your handler namespace matches your command namespace:
640
- - Command: `Customer::Commands::Create`
641
- - Handler: `Customer::CommandHandlers::Create` (not `CustomerCommandHandlers::Create`)
757
+ **Solutions:**
758
+ 1. Ensure your handler namespace matches your command namespace:
759
+ - Command: `Customer::Commands::Create`
760
+ - Handler: `Customer::CommandHandlers::Create` (not `CustomerCommandHandlers::Create`)
761
+ 2. Register your command handlers using the registry pattern shown above
642
762
 
643
763
  ### undefined method 'events' for Customer
644
764
 
@@ -5,12 +5,14 @@ module RailsSimpleEventSourcing
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
+ attribute :write_access_enabled, :boolean, default: false
9
+
8
10
  def readonly?
9
- super || !@write_access_enabled
11
+ super || !write_access_enabled
10
12
  end
11
13
 
12
14
  def enable_write_access!
13
- @write_access_enabled = true
15
+ self.write_access_enabled = true
14
16
  end
15
17
  end
16
18
  end
@@ -17,11 +17,26 @@ module RailsSimpleEventSourcing
17
17
  private
18
18
 
19
19
  def initialize_command_handler
20
- handler_class_name = @command.class.to_s.gsub('::Commands::', '::CommandHandlers::')
21
- handler_class = handler_class_name.safe_constantize
22
- raise CommandHandlerNotFoundError, "Handler #{handler_class_name} not found" unless handler_class
20
+ handler_class = find_handler_class
21
+ raise CommandHandlerNotFoundError, handler_not_found_message unless handler_class
23
22
 
24
23
  handler_class.new(command: @command)
25
24
  end
25
+
26
+ def find_handler_class
27
+ handler_class = CommandHandlerRegistry.handler_for(@command.class)
28
+
29
+ if handler_class.nil? && RailsSimpleEventSourcing.config.use_naming_convention_fallback
30
+ handler_class_name = @command.class.to_s.gsub('::Commands::', '::CommandHandlers::')
31
+ handler_class = handler_class_name.safe_constantize
32
+ end
33
+
34
+ handler_class
35
+ end
36
+
37
+ def handler_not_found_message
38
+ "No handler registered for #{@command.class}. " \
39
+ "Register one with CommandHandlerRegistry.register(#{@command.class}, YourHandlerClass)"
40
+ end
26
41
  end
27
42
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsSimpleEventSourcing
4
+ class CommandHandlerRegistry
5
+ def self.register(command_class, handler_class)
6
+ registry[command_class] = handler_class
7
+ end
8
+
9
+ def self.handler_for(command_class)
10
+ registry[command_class]
11
+ end
12
+
13
+ def self.registry
14
+ @registry ||= {}
15
+ end
16
+
17
+ def self.clear
18
+ @registry = {}
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsSimpleEventSourcing
4
+ class Configuration
5
+ attr_accessor :use_naming_convention_fallback
6
+
7
+ def initialize
8
+ @use_naming_convention_fallback = true
9
+ end
10
+ end
11
+ end
@@ -8,6 +8,7 @@ require_relative 'commands/base'
8
8
  require_relative 'event_applicator'
9
9
  require_relative 'event_player'
10
10
  require_relative 'result'
11
+ require_relative 'command_handler_registry'
11
12
 
12
13
  module RailsSimpleEventSourcing
13
14
  class Engine < ::Rails::Engine
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSimpleEventSourcing
4
- VERSION = '1.0.3'
4
+ VERSION = '1.0.4'
5
5
  end
@@ -2,7 +2,16 @@
2
2
 
3
3
  require 'rails_simple_event_sourcing/version'
4
4
  require 'rails_simple_event_sourcing/engine'
5
+ require 'rails_simple_event_sourcing/configuration'
6
+ require 'rails_simple_event_sourcing/command_handler_registry'
5
7
 
6
8
  module RailsSimpleEventSourcing
7
- # Your code goes here...
9
+ def self.configure
10
+ yield(config) if block_given?
11
+ config
12
+ end
13
+
14
+ def self.config
15
+ @config ||= Configuration.new
16
+ end
8
17
  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.3
4
+ version: 1.0.4
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-13 00:00:00.000000000 Z
11
+ date: 2026-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -91,8 +91,10 @@ files:
91
91
  - lib/rails_simple_event_sourcing/aggregate_repository.rb
92
92
  - lib/rails_simple_event_sourcing/apply_with_returning_aggregate.rb
93
93
  - lib/rails_simple_event_sourcing/command_handler.rb
94
+ - lib/rails_simple_event_sourcing/command_handler_registry.rb
94
95
  - lib/rails_simple_event_sourcing/command_handlers/base.rb
95
96
  - lib/rails_simple_event_sourcing/commands/base.rb
97
+ - lib/rails_simple_event_sourcing/configuration.rb
96
98
  - lib/rails_simple_event_sourcing/engine.rb
97
99
  - lib/rails_simple_event_sourcing/event_applicator.rb
98
100
  - lib/rails_simple_event_sourcing/event_player.rb