event_sourcery 1.0.1 → 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: 101a542737fe302d06e518887e0663fc1855c331f17199cb00f8e43dbe5a16c5
4
- data.tar.gz: ffe47fc0617d3ad85978324d56e971226fab0269ed1d95c3bb8212567aa64415
3
+ metadata.gz: 819d971e975a4fb5a0db2f5b6e7f7605af8d65448125b292ec62854fa0024162
4
+ data.tar.gz: 363d610db1c8c11d04bf815acae6447d7367a006f76cf03388f4cde2e6d76d32
5
5
  SHA512:
6
- metadata.gz: 3c0cfeb363bdfd8ed50d8ecbb73f974fc03aba6f802bd4f70a09f4465e1b27a7217d3c64e517e25af81fc32df531be2cdadc713c517825dc45b3e89f83cb4d16
7
- data.tar.gz: f375efcb3c9d3f09b31e0d01036747db045eb41e74b0feabb87dff069d0a90a52005f6d3829fe8ded28decf32078966c59214dfad2e168aa14759ff7625ef6de
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,24 +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
 
28
32
  spec.add_runtime_dependency 'logger'
29
33
 
34
+ spec.add_development_dependency 'benchmark-ips'
30
35
  spec.add_development_dependency 'bundler'
36
+ spec.add_development_dependency 'pry'
31
37
  spec.add_development_dependency 'rake', '~> 13'
32
38
  spec.add_development_dependency 'rspec'
33
- spec.add_development_dependency 'pry'
34
- spec.add_development_dependency 'benchmark-ips'
39
+ spec.add_development_dependency 'rubocop', '~> 1'
35
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.1'.freeze
2
+ # The version of the EventSourcery gem
3
+ VERSION = '1.0.2'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_sourcery
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Envato
@@ -24,7 +24,7 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: bundler
27
+ name: benchmark-ips
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
@@ -38,21 +38,21 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: rake
41
+ name: bundler
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '13'
46
+ version: '0'
47
47
  type: :development
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '13'
53
+ version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: rspec
55
+ name: pry
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
@@ -66,21 +66,21 @@ dependencies:
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  - !ruby/object:Gem::Dependency
69
- name: pry
69
+ name: rake
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ">="
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0'
74
+ version: '13'
75
75
  type: :development
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - ">="
79
+ - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0'
81
+ version: '13'
82
82
  - !ruby/object:Gem::Dependency
83
- name: benchmark-ips
83
+ name: rspec
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
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'
96
110
  description: ''
97
111
  email:
98
112
  - rubygems@envato.com
@@ -137,11 +151,14 @@ files:
137
151
  - lib/event_sourcery/rspec/event_store_shared_examples.rb
138
152
  - lib/event_sourcery/version.rb
139
153
  homepage: https://github.com/envato/event_sourcery
140
- licenses: []
154
+ licenses:
155
+ - MIT
141
156
  metadata:
157
+ allowed_push_host: https://rubygems.org
142
158
  bug_tracker_uri: https://github.com/envato/event_sourcery/issues
143
159
  changelog_uri: https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md
144
- source_code_uri: https://github.com/envato/event_sourcery
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
145
162
  rdoc_options: []
146
163
  require_paths:
147
164
  - lib
@@ -149,7 +166,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
166
  requirements:
150
167
  - - ">="
151
168
  - !ruby/object:Gem::Version
152
- version: 2.2.0
169
+ version: 2.6.0
153
170
  required_rubygems_version: !ruby/object:Gem::Requirement
154
171
  requirements:
155
172
  - - ">="