event_sourcery 1.0.0 → 1.0.2

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: c501d64a75ded41a08829c20ccf9bf9368c9227b7381023d7d2fe0d502ee8003
4
- data.tar.gz: 22eb6a9e837155b0315377873e7bb17cf6dadb75ef402c78a47ae9ae1c71e5ba
3
+ metadata.gz: 819d971e975a4fb5a0db2f5b6e7f7605af8d65448125b292ec62854fa0024162
4
+ data.tar.gz: 363d610db1c8c11d04bf815acae6447d7367a006f76cf03388f4cde2e6d76d32
5
5
  SHA512:
6
- metadata.gz: a3251f8d9966e191485a24c582ab616c39d2383caa551ea0efcc988eacf08c52104cd0b7a1121c210dbde2975d3becc4f210b93a964bf08a3fc46accfbf0109f
7
- data.tar.gz: c621abddc5fa626fbc0fa02d0be371d25f18ed97d78f4f43c29f9fc6ffa460e6f698f0eccf55f1b9d61a2e0dddb8ec2036905b8e67c876fc9fe7962dfc73783f
6
+ metadata.gz: e6c1d51e23d2d11efb4002e0350c5458fcefb3d8cc40a7e4a660d8b8064b7b887d68311615d4913f80af3a104560669f8769f4bfbca4a826e10f5b8fe1303d5d
7
+ data.tar.gz: c9c68fd9366316f52f4774f69aac877e0a69d92280e5f4c297eb6e31e07d5572e075624e0835bb6dedc5a89cfb08e9ac257964f30d2ab20d8627ba679815e306
data/CHANGELOG.md CHANGED
@@ -7,11 +7,37 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ [Unreleased]: https://github.com/envato/event_sourcery/compare/v1.0.2...HEAD
11
+
12
+ ## [1.0.2] - 2025-12-28
13
+
14
+ ### Changed
15
+
16
+ - Minor fixups in gem metadata ([#240]).
17
+ - Resolve issues as identified by RuboCop ([#241], [#244]).
18
+ - Removed support for older Ruby versions: Ruby 2.6 or greater is now required ([#243]).
19
+
20
+ [1.0.2]: https://github.com/envato/event_sourcery/compare/v1.0.1...v1.0.2
21
+ [#240]: https://github.com/envato/event_sourcery/pull/240
22
+ [#241]: https://github.com/envato/event_sourcery/pull/241
23
+ [#243]: https://github.com/envato/event_sourcery/pull/243
24
+ [#244]: https://github.com/envato/event_sourcery/pull/244
25
+
26
+ ## [1.0.1] - 2025-12-27
27
+
28
+ ### Changed
29
+
30
+ - Support Ruby 4.0, make `logger` dependency explicit ([#239])
31
+
32
+ [1.0.1]: https://github.com/envato/event_sourcery/compare/v1.0.0...v1.0.1
33
+ [#239]: https://github.com/envato/event_sourcery/pull/239
34
+
10
35
  ## [1.0.0] - 2023-11-29
11
36
 
12
37
  ### Removed
13
38
  - Removed Event.persisted? as it was potentially misleading ([#235])
14
39
 
40
+ [1.0.0]: https://github.com/envato/event_sourcery/compare/v0.24.0...v1.0.0
15
41
  [#235]: https://github.com/envato/event_sourcery/pull/235
16
42
 
17
43
  ## [0.24.0] - 2021-11-18
@@ -261,8 +287,6 @@ moving all Postgres related code into a separate gem.
261
287
  - EventSourcery no longer depends on Virtus.
262
288
  - `Command` and `CommandHandler` have been removed.
263
289
 
264
- [Unreleased]: https://github.com/envato/event_sourcery/compare/v0.24.0...HEAD
265
- [1.0.0]: https://github.com/envato/event_sourcery/compare/v0.24.0...v1.0.0
266
290
  [0.24.0]: https://github.com/envato/event_sourcery/compare/v0.23.1...v0.24.0
267
291
  [0.23.1]: https://github.com/envato/event_sourcery/compare/v0.23.0...v0.23.1
268
292
  [0.23.0]: https://github.com/envato/event_sourcery/compare/v0.22.0...v0.23.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EventSourcery
2
2
 
3
- [![Build Status](https://github.com/envato/event_sourcery/workflows/tests/badge.svg?branch=main)](https://github.com/envato/event_sourcery/actions?query=workflow%3Atests+branch%3Amain)
3
+ [![Build Status](https://github.com/envato/event_sourcery/actions/workflows/test.yml/badge.svg)](https://github.com/envato/event_sourcery/actions/workflows/test.yml)
4
4
 
5
5
  A framework for building event sourced, CQRS applications.
6
6
 
@@ -34,7 +34,7 @@ A framework for building event sourced, CQRS applications.
34
34
 
35
35
  ## Development Status
36
36
 
37
- [![Build Status](https://github.com/envato/event_sourcery/workflows/tests/badge.svg?branch=main)](https://github.com/envato/event_sourcery/actions?query=branch%3Amain)
37
+ [![Build Status](https://github.com/envato/event_sourcery/actions/workflows/test.yml/badge.svg)](https://github.com/envato/event_sourcery/actions/workflows/test.yml)
38
38
 
39
39
  Event Sourcery is in production use at [Envato](http://envato.com).
40
40
 
@@ -1,5 +1,4 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'event_sourcery/version'
5
4
 
@@ -12,22 +11,30 @@ Gem::Specification.new do |spec|
12
11
  spec.summary = 'Event Sourcing Library'
13
12
  spec.description = ''
14
13
  spec.homepage = 'https://github.com/envato/event_sourcery'
14
+ spec.license = 'MIT'
15
15
  spec.metadata = {
16
- 'bug_tracker_uri' => 'https://github.com/envato/event_sourcery/issues',
17
- 'changelog_uri' => 'https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md',
18
- 'source_code_uri' => 'https://github.com/envato/event_sourcery',
19
- }
16
+ 'allowed_push_host' => 'https://rubygems.org',
17
+ 'bug_tracker_uri' => "#{spec.homepage}/issues",
18
+ 'changelog_uri' => "#{spec.homepage}/blob/HEAD/CHANGELOG.md",
19
+ 'documentation_uri' => "https://www.rubydoc.info/gems/event_sourcery/#{spec.version}",
20
+ 'source_code_uri' => "#{spec.homepage}/tree/v#{spec.version}"
21
+ }
20
22
 
21
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(\.|Gemfile|Rakefile|bin/|script/|spec/)}) }
23
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
+ f.match(%r{^(\.|Gemfile|Rakefile|bin/|script/|spec/)})
25
+ end
22
26
  spec.bindir = 'exe'
23
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
28
  spec.require_paths = ['lib']
25
29
 
26
- spec.required_ruby_version = '>= 2.2.0'
30
+ spec.required_ruby_version = '>= 2.6.0'
27
31
 
32
+ spec.add_runtime_dependency 'logger'
33
+
34
+ spec.add_development_dependency 'benchmark-ips'
28
35
  spec.add_development_dependency 'bundler'
36
+ spec.add_development_dependency 'pry'
29
37
  spec.add_development_dependency 'rake', '~> 13'
30
38
  spec.add_development_dependency 'rspec'
31
- spec.add_development_dependency 'pry'
32
- spec.add_development_dependency 'benchmark-ips'
39
+ spec.add_development_dependency 'rubocop', '~> 1'
33
40
  end
@@ -3,7 +3,9 @@ module EventSourcery
3
3
  # EventSourcery::AggregateRoot provides a foundation for writing your own aggregate root classes.
4
4
  # You can use it by including it in your classes, as show in the example code.
5
5
  #
6
- # Excerpt from {https://github.com/envato/event_sourcery/blob/HEAD/docs/core-concepts.md EventSourcery Core Concepts} on Aggregates follows:
6
+ # Excerpt from {https://github.com/envato/event_sourcery/blob/HEAD/docs/core-concepts.md EventSourcery Core Concepts}
7
+ # on Aggregates follows:
8
+ #
7
9
  # === Aggregates and Command Handling
8
10
  #
9
11
  # An aggregate is a cluster of domain objects that can be treated as a single unit.
@@ -18,7 +20,8 @@ module EventSourcery
18
20
  # A typical EventSourcery application will have one or more aggregate roots with multiple commands.
19
21
  #
20
22
  # The following partial example is taken from the EventSourceryTodoApp.
21
- # Refer a more complete example {https://github.com/envato/event_sourcery_todo_app/blob/HEAD/app/aggregates/todo.rb here}.
23
+ # Refer a more complete example
24
+ # {https://github.com/envato/event_sourcery_todo_app/blob/HEAD/app/aggregates/todo.rb here}.
22
25
  #
23
26
  # @example
24
27
  # module EventSourceryTodoApp
@@ -85,7 +88,9 @@ module EventSourcery
85
88
  #
86
89
  # @param id [String] ID (a UUID represented as a string) of the aggregate instance to be loaded
87
90
  # @param events [Array] Events from which the aggregate's current state will be formed
88
- # @param on_unknown_event [Proc] Optional. The proc to be run if an unknown event type (for which no event handler is registered using {ClassMethods#apply}) is to be loaded.
91
+ # @param on_unknown_event [Proc] Optional. The proc to be run if an unknown
92
+ # event type (for which no event handler is registered using
93
+ # {ClassMethods#apply}) is to be loaded.
89
94
  def initialize(id, events, on_unknown_event: EventSourcery.config.on_unknown_event)
90
95
  @id = id.to_str
91
96
  @version = 0
@@ -76,7 +76,9 @@ module EventSourcery
76
76
  # ({EventStore::EventBuilder}). By default {EventBodySerializer} will be used.
77
77
  # Provide a custom serializer here to change how the event body is serialized.
78
78
  def event_body_serializer
79
- @event_body_serializer ||= EventBodySerializer.new
79
+ @event_body_serializer ||=
80
+ EventBodySerializer
81
+ .new
80
82
  .add(Hash, EventBodySerializer::HashSerializer)
81
83
  .add(Array, EventBodySerializer::ArraySerializer)
82
84
  .add(Time, &:iso8601)
@@ -7,9 +7,9 @@ module EventSourcery
7
7
  #
8
8
  # Will return `nil` if called on an instance of {EventSourcery::Event}.
9
9
  def self.type
10
- unless self == Event
11
- EventSourcery.config.event_type_serializer.serialize(self)
12
- end
10
+ return if self == Event
11
+
12
+ EventSourcery.config.event_type_serializer.serialize(self)
13
13
  end
14
14
 
15
15
  # Use this method to add "upcasting" to your events.
@@ -61,19 +61,35 @@ module EventSourcery
61
61
  # @return [Time] Created at timestamp (in UTC) for the event.
62
62
 
63
63
  # @!attribute [r] correlation_id
64
- # @return [String] UUID attached to the event that allows reference to a particular transaction or event chain. This value is often supplied as part of a command issued by clients.
64
+ # @return [String] UUID attached to the event that allows reference to a
65
+ # particular transaction or event chain. This value is often supplied as
66
+ # part of a command issued by clients.
65
67
 
66
68
  # @!attribute [r] causation_id
67
69
  # @return [String] UUID of the event that caused this event.
68
70
 
69
71
  #
70
- # @param id [Integer] Optional. Unique identifier at the persistent layer. By default this will be set by the underlying persistence layer when persisting the event.
71
- # @param uuid [String] UUID as a string. Optional. Unique identifier for this event. A random UUID will be generated by default.
72
- # @param aggregate_id [String] UUID as a string. Aggregate instance UUID to which this event belongs to.
72
+ # @param id [Integer] Optional. Unique identifier at the persistent layer.
73
+ # By default this will be set by the underlying persistence layer when
74
+ # persisting the event.
75
+ #
76
+ # @param uuid [String] UUID as a string. Optional. Unique identifier for
77
+ # this event. A random UUID will be generated by default.
78
+ #
79
+ # @param aggregate_id [String] UUID as a string. Aggregate instance UUID to
80
+ # which this event belongs to.
81
+ #
73
82
  # @param type [Class] Optional. Event type. {Event.type} will be used by default.
74
- # @param version [String] Optional. Event's aggregate version. Used by some event stores to guard against concurrency errors.
83
+ #
84
+ # @param version [String] Optional. Event's aggregate version. Used by some
85
+ # event stores to guard against concurrency errors.
86
+ #
75
87
  # @param created_at [Time] Optional. Created at timestamp (in UTC) for the event.
76
- # @param correlation_id [String] Optional. UUID attached to the event that allows reference to a particular transaction or event chain. This value is often supplied as part of a command issued by clients.
88
+ #
89
+ # @param correlation_id [String] Optional. UUID attached to the event that
90
+ # allows reference to a particular transaction or event chain. This value
91
+ # is often supplied as part of a command issued by clients.
92
+ #
77
93
  # @param causation_id [String] Optional. UUID of the event that caused this event.
78
94
  def initialize(id: nil,
79
95
  uuid: SecureRandom.uuid,
@@ -143,15 +159,15 @@ module EventSourcery
143
159
  # @return Hash
144
160
  def to_h
145
161
  {
146
- id: id,
147
- uuid: uuid,
148
- aggregate_id: aggregate_id,
149
- type: type,
150
- body: body,
151
- version: version,
152
- created_at: created_at,
162
+ id: id,
163
+ uuid: uuid,
164
+ aggregate_id: aggregate_id,
165
+ type: type,
166
+ body: body,
167
+ version: version,
168
+ created_at: created_at,
153
169
  correlation_id: correlation_id,
154
- causation_id: causation_id,
170
+ causation_id: causation_id
155
171
  }
156
172
  end
157
173
  end
@@ -4,7 +4,9 @@ module EventSourcery
4
4
  # Serialize the given event body, with the default or the provided, serializer
5
5
  #
6
6
  # @param event_body event body to be serialized
7
- # @param serializer Optional. Serializer to be used. By default {Config#event_body_serializer EventSourcery.config.event_body_serializer} will be used.
7
+ # @param serializer Optional. Serializer to be used. By default
8
+ # {Config#event_body_serializer EventSourcery.config.event_body_serializer}
9
+ # will be used.
8
10
  def self.serialize(event_body,
9
11
  serializer: EventSourcery.config.event_body_serializer)
10
12
  serializer.serialize(event_body)
@@ -9,7 +9,7 @@ module EventSourcery
9
9
  #
10
10
  # @api private
11
11
  DEFAULT_RETRY_INTERVAL = 1
12
-
12
+
13
13
  def initialize(processor_name:)
14
14
  @processor_name = processor_name
15
15
  @retry_interval = DEFAULT_RETRY_INTERVAL
@@ -18,8 +18,8 @@ module EventSourcery
18
18
  # Will yield the block and attempt to retry after a defined retry interval {DEFAULT_RETRY_INTERVAL}.
19
19
  def with_error_handling
20
20
  yield
21
- rescue => error
22
- report_error(error)
21
+ rescue StandardError => e
22
+ report_error(e)
23
23
  sleep(@retry_interval)
24
24
 
25
25
  retry
@@ -23,11 +23,13 @@ module EventSourcery
23
23
  # Will yield the block and attempt to retry in an exponential backoff.
24
24
  def with_error_handling
25
25
  yield
26
- rescue => error
27
- report_error(error)
28
- update_retry_interval(error)
26
+ rescue StandardError => e
27
+ report_error(e)
28
+ update_retry_interval(e)
29
29
  sleep(@retry_interval)
30
- EventSourcery.logger.info { "Retrying #{@processor_name} with error: #{error.message} at interval=#{@retry_interval}" }
30
+ EventSourcery.logger.info do
31
+ "Retrying #{@processor_name} with error: #{e.message} at interval=#{@retry_interval}"
32
+ end
31
33
  retry
32
34
  end
33
35
 
@@ -10,8 +10,8 @@ module EventSourcery
10
10
  # Will yield the block and exit the process if an error is raised.
11
11
  def with_error_handling
12
12
  yield
13
- rescue => error
14
- report_error(error)
13
+ rescue StandardError => e
14
+ report_error(e)
15
15
  Process.exit(false)
16
16
  end
17
17
  end
@@ -1,7 +1,7 @@
1
1
  module EventSourcery
2
2
  module EventProcessing
3
3
  class ESPProcess
4
- DEFAULT_AFTER_FORK = -> (event_processor) { }
4
+ DEFAULT_AFTER_FORK = ->(event_processor) {}
5
5
 
6
6
  def initialize(event_processor:,
7
7
  event_source:,
@@ -30,9 +30,7 @@ module EventSourcery
30
30
  record_terminated_processes
31
31
  end
32
32
  terminate_remaining_processes
33
- until all_processes_terminated? || waited_long_enough?
34
- record_terminated_processes
35
- end
33
+ record_terminated_processes until all_processes_terminated? || waited_long_enough?
36
34
  kill_remaining_processes
37
35
  record_terminated_processes until all_processes_terminated?
38
36
  end
@@ -43,7 +41,7 @@ module EventSourcery
43
41
  process = ESPProcess.new(
44
42
  event_processor: event_processor,
45
43
  event_source: @event_source,
46
- after_fork: @after_fork,
44
+ after_fork: @after_fork
47
45
  )
48
46
  pid = Process.fork { process.start }
49
47
  @pids[pid] = event_processor
@@ -72,7 +70,7 @@ module EventSourcery
72
70
  end
73
71
 
74
72
  def listen_for_shutdown_signals
75
- %i(TERM INT).each do |signal|
73
+ %i[TERM INT].each do |signal|
76
74
  Signal.trap(signal) { shutdown }
77
75
  end
78
76
  end
@@ -81,6 +79,7 @@ module EventSourcery
81
79
  loop do
82
80
  yield
83
81
  break if shutdown_requested?
82
+
84
83
  sleep(1)
85
84
  end
86
85
  end
@@ -109,6 +108,7 @@ module EventSourcery
109
108
  logger.info("ESPRunner: Process #{event_processor&.processor_name || pid} " \
110
109
  "terminated with exit status: #{status.exitstatus.inspect}")
111
110
  next unless event_processor
111
+
112
112
  @exit_status &&= !!status.success?
113
113
  @after_subprocess_termination&.call(processor: event_processor, runner: self, exit_status: status.exitstatus)
114
114
  end
@@ -31,13 +31,12 @@ module EventSourcery
31
31
  instance_exec(event, &handler)
32
32
  end
33
33
  @_event = nil
34
- rescue
34
+ rescue StandardError
35
35
  raise EventProcessingError.new(event: event, processor: self)
36
36
  end
37
37
  end
38
38
 
39
39
  module ClassMethods
40
-
41
40
  # @attr_reader processes_event_types [Array] Process Event Types
42
41
  # @attr_reader event_handlers [Hash] Hash of handler blocks keyed by event
43
42
  # @attr_reader all_event_handler [Proc] An event handler
@@ -75,9 +74,9 @@ module EventSourcery
75
74
  if event_classes.empty?
76
75
  if @all_event_handler
77
76
  raise MultipleCatchAllHandlersDefined, 'Attemping to define multiple catch all event handlers.'
78
- else
79
- @all_event_handler = block
80
77
  end
78
+
79
+ @all_event_handler = block
81
80
  else
82
81
  @processes_event_types ||= []
83
82
  event_classes.each do |event_class|
@@ -127,8 +126,8 @@ module EventSourcery
127
126
  def subscribe_to(event_source, subscription_master: EventStore::SignalHandlingSubscriptionMaster.new)
128
127
  setup
129
128
  event_source.subscribe(from_id: last_processed_event_id + 1,
130
- event_types: processes_event_types,
131
- subscription_master: subscription_master) do |events|
129
+ event_types: processes_event_types,
130
+ subscription_master: subscription_master) do |events|
132
131
  process_events(events, subscription_master)
133
132
  end
134
133
  end
@@ -15,9 +15,7 @@ module EventSourcery
15
15
  break
16
16
  end
17
17
  end
18
- unless no_events_left
19
- event_id = events.last.id + 1
20
- end
18
+ event_id = events.last.id + 1 unless no_events_left
21
19
  end while !caught_up && !no_events_left
22
20
  end
23
21
  end
@@ -4,11 +4,11 @@ module EventSourcery
4
4
  # To support legacy implementations. Type is provided when initializing
5
5
  # the event, not derived from the class constant
6
6
  class Legacy
7
- def serialize(event_class)
7
+ def serialize(_event_class)
8
8
  nil
9
9
  end
10
10
 
11
- def deserialize(event_type)
11
+ def deserialize(_event_type)
12
12
  Event
13
13
  end
14
14
  end
@@ -13,32 +13,35 @@ module EventSourcery
13
13
  def underscore(camel_cased_word)
14
14
  word = camel_cased_word.to_s.dup
15
15
  word.gsub!(/::/, '/')
16
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
17
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
18
- word.tr!("-", "_")
16
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
17
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
18
+ word.tr!('-', '_')
19
19
  word.downcase!
20
20
  word
21
21
  end
22
22
 
23
23
  def camelize(term, uppercase_first_letter = true)
24
24
  string = term.to_s
25
- if uppercase_first_letter
26
- string = string.sub(/^[a-z\d]*/) { capitalize($&) }
27
- else
28
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
29
- end
30
- string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{capitalize($2)}" }.gsub('/', '::')
25
+ string =
26
+ if uppercase_first_letter
27
+ string.sub(/^[a-z\d]*/) { capitalize(::Regexp.last_match(0)) }
28
+ else
29
+ string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { ::Regexp.last_match(0).downcase }
30
+ end
31
+ string.gsub(%r{(?:_|(/))([a-z\d]*)}i) do
32
+ "#{::Regexp.last_match(1)}#{capitalize(::Regexp.last_match(2))}"
33
+ end.gsub('/', '::')
31
34
  end
32
35
 
33
36
  private
34
37
 
35
38
  def capitalize(lower_case_and_underscored_word)
36
39
  result = lower_case_and_underscored_word.to_s.dup
37
- result.gsub!(/_id$/, "")
40
+ result.gsub!(/_id$/, '')
38
41
  result.gsub!(/_/, ' ')
39
- result.gsub(/([a-z\d]*)/i) { |match|
42
+ result.gsub(/([a-z\d]*)/i) do |match|
40
43
  "#{match.downcase}"
41
- }.gsub(/^\w/) { $&.upcase }
44
+ end.gsub(/^\w/) { ::Regexp.last_match(0).upcase }
42
45
  end
43
46
  end
44
47
 
@@ -1,7 +1,8 @@
1
1
  module EventSourcery
2
2
  module EventStore
3
-
4
- # This class provides a basic poll waiter implementation that calls the provided block and sleeps for the specified interval, to be used by a {Subscription}.
3
+ # This class provides a basic poll waiter implementation that calls the
4
+ # provided block and sleeps for the specified interval, to be used by a
5
+ # {Subscription}.
5
6
  class PollWaiter
6
7
  #
7
8
  # @param interval [Float] Optional. Will default to `0.5`
@@ -9,7 +10,8 @@ module EventSourcery
9
10
  @interval = interval
10
11
  end
11
12
 
12
- # Start polling. Call the provided block and sleep. Repeat until `:stop` is thrown (usually via a subscription master).
13
+ # Start polling. Call the provided block and sleep. Repeat until `:stop`
14
+ # is thrown (usually via a subscription master).
13
15
  #
14
16
  # @param block [Proc] code block to be called when polling
15
17
  #
@@ -20,7 +20,7 @@ module EventSourcery
20
20
  private
21
21
 
22
22
  def setup_graceful_shutdown
23
- %i(TERM INT).each do |signal|
23
+ %i[TERM INT].each do |signal|
24
24
  Signal.trap(signal) do
25
25
  @shutdown_requested = true
26
26
  wakeup_main_thread
@@ -1,6 +1,5 @@
1
1
  module EventSourcery
2
2
  module EventStore
3
-
4
3
  # This allows Event Stream Processors (ESPs) to subscribe to an event store, and be notified when new events are
5
4
  # added.
6
5
  class Subscription
@@ -10,14 +9,16 @@ module EventSourcery
10
9
  # @param from_event_id [Integer] Start reading events from this event ID
11
10
  # @param event_types [Array] Optional. If specified, only subscribe to given event types.
12
11
  # @param on_new_events [Proc] Code block to be executed when new events are received
13
- # @param subscription_master A subscription master instance (such as {EventStore::SignalHandlingSubscriptionMaster}) which orchestrates a graceful shutdown of the subscription, if one is requested.
12
+ # @param subscription_master A subscription master instance (such as
13
+ # {EventStore::SignalHandlingSubscriptionMaster}) which orchestrates a
14
+ # graceful shutdown of the subscription, if one is requested.
14
15
  # @param events_table_name [Symbol] Optional. Defaults to `:events`
15
16
  def initialize(event_store:,
16
17
  poll_waiter:,
17
18
  from_event_id:,
18
- event_types: nil,
19
19
  on_new_events:,
20
20
  subscription_master:,
21
+ event_types: nil,
21
22
  events_table_name: :events,
22
23
  batch_size: EventSourcery.config.subscription_batch_size)
23
24
  @event_store = event_store
@@ -51,6 +52,7 @@ module EventSourcery
51
52
  @subscription_master.shutdown_if_requested
52
53
  events = @event_store.get_next_from(@current_event_id + 1, event_types: @event_types, limit: batch_size)
53
54
  break if events.empty?
55
+
54
56
  EventSourcery.logger.debug { "New events in subscription: #{events.inspect}" }
55
57
  @on_new_events.call(events)
56
58
  @current_event_id = events.last.id
@@ -19,7 +19,6 @@ module EventSourcery
19
19
  def event_sink
20
20
  @event_sink ||= ::EventSourcery::EventStore::EventSink.new(event_store)
21
21
  end
22
-
23
22
  end
24
23
 
25
24
  def self.configure
@@ -29,6 +28,5 @@ module EventSourcery
29
28
  def self.config
30
29
  @config ||= Config.new
31
30
  end
32
-
33
31
  end
34
32
  end
@@ -18,16 +18,17 @@ module EventSourcery
18
18
  # Store given events to the in-memory store
19
19
  #
20
20
  # @param event_or_events Event(s) to be stored
21
- # @param expected_version [Optional] Expected version for the aggregate. This is the version the caller of this method expect the aggregate to be in. If it's different from the expected version a {EventSourcery::ConcurrencyError} will be raised. Defaults to nil.
21
+ # @param expected_version [Optional] Expected version for the aggregate.
22
+ # This is the version the caller of this method expect the aggregate to
23
+ # be in. If it's different from the expected version a
24
+ # {EventSourcery::ConcurrencyError} will be raised. Defaults to nil.
22
25
  # @raise EventSourcery::ConcurrencyError
23
26
  # @return Boolean
24
27
  def sink(event_or_events, expected_version: nil)
25
28
  events = Array(event_or_events)
26
29
  ensure_one_aggregate(events)
27
30
 
28
- if expected_version && version_for(events.first.aggregate_id) != expected_version
29
- raise ConcurrencyError
30
- end
31
+ raise ConcurrencyError if expected_version && version_for(events.first.aggregate_id) != expected_version
31
32
 
32
33
  events.each do |event|
33
34
  @events << @event_builder.build(
@@ -39,7 +40,7 @@ module EventSourcery
39
40
  created_at: event.created_at || Time.now.utc,
40
41
  uuid: event.uuid,
41
42
  correlation_id: event.correlation_id,
42
- causation_id: event.causation_id,
43
+ causation_id: event.causation_id
43
44
  )
44
45
  end
45
46
 
@@ -55,11 +56,12 @@ module EventSourcery
55
56
  # @param limit [Integer] Optional. Number of events to retrieve (starting from the given event ID).
56
57
  # @return Array
57
58
  def get_next_from(id, event_types: nil, limit: 1000)
58
- events = if event_types.nil?
59
- @events
60
- else
61
- @events.select { |e| event_types.include?(e.type) }
62
- end
59
+ events =
60
+ if event_types.nil?
61
+ @events
62
+ else
63
+ @events.select { |e| event_types.include?(e.type) }
64
+ end
63
65
 
64
66
  events.select { |event| event.id >= id }.first(limit)
65
67
  end
@@ -69,11 +71,12 @@ module EventSourcery
69
71
  # @param event_types [Array] Optional. If supplied, only retrieve events of given type(s).
70
72
  # @return Integer
71
73
  def latest_event_id(event_types: nil)
72
- events = if event_types.nil?
73
- @events
74
- else
75
- @events.select { |e| event_types.include?(e.type) }
76
- end
74
+ events =
75
+ if event_types.nil?
76
+ @events
77
+ else
78
+ @events.select { |e| event_types.include?(e.type) }
79
+ end
77
80
 
78
81
  events.empty? ? 0 : events.last.id
79
82
  end
@@ -108,9 +111,9 @@ module EventSourcery
108
111
  # @param events [Array] Collection of events
109
112
  # @raise AtomicWriteToMultipleAggregatesNotSupported
110
113
  def ensure_one_aggregate(events)
111
- unless events.map(&:aggregate_id).uniq.one?
112
- raise AtomicWriteToMultipleAggregatesNotSupported
113
- end
114
+ return if events.map(&:aggregate_id).uniq.one?
115
+
116
+ raise AtomicWriteToMultipleAggregatesNotSupported
114
117
  end
115
118
 
116
119
  # Adds a listener or listeners to the memory store.
@@ -1,7 +1,6 @@
1
1
  module EventSourcery
2
2
  module Memory
3
3
  module Projector
4
-
5
4
  def self.included(base)
6
5
  base.include(EventSourcery::EventProcessing::EventStreamProcessor)
7
6
  base.include(InstanceMethods)
@@ -26,7 +26,7 @@ module EventSourcery
26
26
  @state[processor_name.to_s] = event_id
27
27
  end
28
28
 
29
- alias :reset_last_processed_event_id :setup
29
+ alias reset_last_processed_event_id setup
30
30
 
31
31
  # Find the last processed event id for a given processor name.
32
32
  #
@@ -51,22 +51,22 @@ RSpec.shared_examples 'an event store' do
51
51
  end
52
52
 
53
53
  it 'writes multiple events' do
54
- event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
55
- ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2}),
56
- ItemAdded.new(aggregate_id: aggregate_id, body: {e: 3})])
54
+ event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
55
+ ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 }),
56
+ ItemAdded.new(aggregate_id: aggregate_id, body: { e: 3 })])
57
57
  events = event_store.get_next_from(1)
58
58
  expect(events.count).to eq 3
59
59
  expect(events.map(&:id)).to eq [1, 2, 3]
60
- expect(events.map(&:body)).to eq [{'e' => 1}, {'e' => 2}, {'e' => 3}]
60
+ expect(events.map(&:body)).to eq [{ 'e' => 1 }, { 'e' => 2 }, { 'e' => 3 }]
61
61
  expect(events.map(&:version)).to eq [1, 2, 3]
62
62
  end
63
63
 
64
64
  it 'sets the correct aggregates version' do
65
- event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
66
- ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2})])
65
+ event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
66
+ ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 })])
67
67
  # this will throw a unique constrain error if the aggregate version was not set correctly ^
68
- event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
69
- ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2})])
68
+ event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
69
+ ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 })])
70
70
  events = event_store.get_next_from(1)
71
71
  expect(events.count).to eq 4
72
72
  expect(events.map(&:id)).to eq [1, 2, 3, 4]
@@ -104,16 +104,17 @@ RSpec.shared_examples 'an event store' do
104
104
 
105
105
  it 'correctly inserts created at times when inserting multiple events atomically' do
106
106
  time = Time.parse('2016-10-14T00:00:00.646191Z')
107
- event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, created_at: nil), ItemAdded.new(aggregate_id: aggregate_id, created_at: time)])
107
+ event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, created_at: nil),
108
+ ItemAdded.new(aggregate_id: aggregate_id, created_at: time)])
108
109
  created_ats = event_store.get_next_from(0).map(&:created_at)
109
110
  expect(created_ats.map(&:class)).to eq [Time, Time]
110
111
  expect(created_ats.last).to eq time
111
112
  end
112
113
 
113
114
  it 'raises an error if the events given are for more than one aggregate' do
114
- expect {
115
+ expect do
115
116
  event_store.sink([ItemAdded.new(aggregate_id: aggregate_id), ItemAdded.new(aggregate_id: SecureRandom.uuid)])
116
- }.to raise_error(EventSourcery::AtomicWriteToMultipleAggregatesNotSupported)
117
+ end.to raise_error(EventSourcery::AtomicWriteToMultipleAggregatesNotSupported)
117
118
  end
118
119
  end
119
120
 
@@ -168,7 +169,7 @@ RSpec.shared_examples 'an event store' do
168
169
 
169
170
  expect(event_store.latest_event_id(event_types: ['type1'])).to eq 2
170
171
  expect(event_store.latest_event_id(event_types: ['type2'])).to eq 3
171
- expect(event_store.latest_event_id(event_types: ['type1', 'type2'])).to eq 3
172
+ expect(event_store.latest_event_id(event_types: %w[type1 type2])).to eq 3
172
173
  end
173
174
  end
174
175
  end
@@ -207,7 +208,7 @@ RSpec.shared_examples 'an event store' do
207
208
 
208
209
  describe '#each_by_range' do
209
210
  before do
210
- (1..21).each do |i|
211
+ 21.times do
211
212
  event_store.sink(ItemAdded.new(aggregate_id: aggregate_id, body: {}))
212
213
  end
213
214
  end
@@ -255,7 +256,7 @@ RSpec.shared_examples 'an event store' do
255
256
  def save_event(expected_version: nil)
256
257
  event_store.sink(
257
258
  BillingDetailsProvided.new(aggregate_id: aggregate_id, body: { my_event: 'data' }),
258
- expected_version: expected_version,
259
+ expected_version: expected_version
259
260
  )
260
261
  end
261
262
 
@@ -278,9 +279,8 @@ RSpec.shared_examples 'an event store' do
278
279
 
279
280
  context 'and the expected version is incorrect - 1' do
280
281
  it 'raises a ConcurrencyError' do
281
- expect {
282
- save_event(expected_version: 1)
283
- }.to raise_error(EventSourcery::ConcurrencyError)
282
+ expect { save_event(expected_version: 1) }
283
+ .to raise_error(EventSourcery::ConcurrencyError)
284
284
  end
285
285
  end
286
286
 
@@ -299,9 +299,8 @@ RSpec.shared_examples 'an event store' do
299
299
 
300
300
  context 'with an incorrect expected version - 0' do
301
301
  it 'raises a ConcurrencyError' do
302
- expect {
303
- save_event(expected_version: 0)
304
- }.to raise_error(EventSourcery::ConcurrencyError)
302
+ expect { save_event(expected_version: 0) }
303
+ .to raise_error(EventSourcery::ConcurrencyError)
305
304
  end
306
305
  end
307
306
 
@@ -1,4 +1,4 @@
1
1
  module EventSourcery
2
- # Defines the version
3
- VERSION = '1.0.0'.freeze
2
+ # The version of the EventSourcery gem
3
+ VERSION = '1.0.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,23 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_sourcery
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Envato
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-12-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: bundler
13
+ name: logger
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
18
  version: '0'
20
- type: :development
19
+ type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
@@ -25,21 +24,21 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: rake
27
+ name: benchmark-ips
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - "~>"
30
+ - - ">="
32
31
  - !ruby/object:Gem::Version
33
- version: '13'
32
+ version: '0'
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
- - - "~>"
37
+ - - ">="
39
38
  - !ruby/object:Gem::Version
40
- version: '13'
39
+ version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: rspec
41
+ name: bundler
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
@@ -67,7 +66,21 @@ dependencies:
67
66
  - !ruby/object:Gem::Version
68
67
  version: '0'
69
68
  - !ruby/object:Gem::Dependency
70
- name: benchmark-ips
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
71
84
  requirement: !ruby/object:Gem::Requirement
72
85
  requirements:
73
86
  - - ">="
@@ -80,6 +93,20 @@ dependencies:
80
93
  - - ">="
81
94
  - !ruby/object:Gem::Version
82
95
  version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1'
83
110
  description: ''
84
111
  email:
85
112
  - rubygems@envato.com
@@ -124,12 +151,14 @@ files:
124
151
  - lib/event_sourcery/rspec/event_store_shared_examples.rb
125
152
  - lib/event_sourcery/version.rb
126
153
  homepage: https://github.com/envato/event_sourcery
127
- licenses: []
154
+ licenses:
155
+ - MIT
128
156
  metadata:
157
+ allowed_push_host: https://rubygems.org
129
158
  bug_tracker_uri: https://github.com/envato/event_sourcery/issues
130
159
  changelog_uri: https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md
131
- source_code_uri: https://github.com/envato/event_sourcery
132
- post_install_message:
160
+ documentation_uri: https://www.rubydoc.info/gems/event_sourcery/1.0.2
161
+ source_code_uri: https://github.com/envato/event_sourcery/tree/v1.0.2
133
162
  rdoc_options: []
134
163
  require_paths:
135
164
  - lib
@@ -137,15 +166,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
166
  requirements:
138
167
  - - ">="
139
168
  - !ruby/object:Gem::Version
140
- version: 2.2.0
169
+ version: 2.6.0
141
170
  required_rubygems_version: !ruby/object:Gem::Requirement
142
171
  requirements:
143
172
  - - ">="
144
173
  - !ruby/object:Gem::Version
145
174
  version: '0'
146
175
  requirements: []
147
- rubygems_version: 3.3.7
148
- signing_key:
176
+ rubygems_version: 4.0.3
149
177
  specification_version: 4
150
178
  summary: Event Sourcing Library
151
179
  test_files: []