sequent 4.3.0 → 5.0.0

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequent/core/command_record.rb +1 -1
  3. data/lib/sequent/core/helpers/attr_matchers/argument_serializer.rb +35 -0
  4. data/lib/sequent/core/helpers/attr_matchers/attr_matchers.rb +10 -0
  5. data/lib/sequent/core/helpers/attr_matchers/dsl.rb +23 -0
  6. data/lib/sequent/core/helpers/attr_matchers/equals.rb +24 -0
  7. data/lib/sequent/core/helpers/attr_matchers/greater_than.rb +24 -0
  8. data/lib/sequent/core/helpers/attr_matchers/greater_than_equals.rb +24 -0
  9. data/lib/sequent/core/helpers/attr_matchers/less_than.rb +24 -0
  10. data/lib/sequent/core/helpers/attr_matchers/less_than_equals.rb +24 -0
  11. data/lib/sequent/core/helpers/attr_matchers/not_equals.rb +24 -0
  12. data/lib/sequent/core/helpers/attribute_support.rb +34 -9
  13. data/lib/sequent/core/helpers/autoset_attributes.rb +5 -5
  14. data/lib/sequent/core/helpers/message_dispatcher.rb +20 -0
  15. data/lib/sequent/core/helpers/message_handler.rb +62 -8
  16. data/lib/sequent/core/helpers/message_handler_option_registry.rb +59 -0
  17. data/lib/sequent/core/helpers/message_matchers/any.rb +34 -0
  18. data/lib/sequent/core/helpers/message_matchers/argument_coercer.rb +24 -0
  19. data/lib/sequent/core/helpers/message_matchers/argument_serializer.rb +20 -0
  20. data/lib/sequent/core/helpers/message_matchers/dsl.rb +23 -0
  21. data/lib/sequent/core/helpers/message_matchers/except_opt.rb +24 -0
  22. data/lib/sequent/core/helpers/message_matchers/has_attrs.rb +54 -0
  23. data/lib/sequent/core/helpers/message_matchers/instance_of.rb +24 -0
  24. data/lib/sequent/core/helpers/message_matchers/is_a.rb +34 -0
  25. data/lib/sequent/core/helpers/message_matchers/message_matchers.rb +10 -0
  26. data/lib/sequent/core/helpers/message_router.rb +55 -0
  27. data/lib/sequent/core/workflow.rb +2 -2
  28. data/lib/sequent/test/command_handler_helpers.rb +2 -2
  29. data/lib/version.rb +1 -1
  30. metadata +31 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feea2aeb3dba28570a36615392e4ecc1037bfdfd1c2d667bcc787113e4c7fdba
4
- data.tar.gz: 79dc60b21109885a56f3f68cf97185afee5692167f54db1934eaa89dc9d030de
3
+ metadata.gz: 1dea4b53205452eaa68007ed47688b3fa074229677186e9342265cdd7a31d4db
4
+ data.tar.gz: 2d342b9a013372ca99b441c4980432f642a56741746291d814c68ad60cf6c26e
5
5
  SHA512:
6
- metadata.gz: bb74d9c6cbb946bab9b00cb2ea73eab80caed4737d12ce2115a6c97348ca0e9452a14280b3d0de9ecba237c201fdd8b2914382751569224c89cae92128d5d3f5
7
- data.tar.gz: 5fe084735eb4df8855b8102e68d027ca828a8dd7540d577b6e0a97616bbd22815432e684f20d48d04ad111db962fe935af4bff50afd8ebc7242f5c15f01b97a7
6
+ metadata.gz: cea76a7429f29dcff3e73789f608c968030ade74446493effcf6cf6b51f39c3815da7e5ccc040cf0392a797a6978fa773b5f8f2791f907ebbb31830ab7ae56bc
7
+ data.tar.gz: e03569382132ffefabd44ebc973d78a2c1745291cf64ef17c0dc744bd513d5fe72c816a0cfb69da7297ddd076df81139a53058816f02bc215f58ad48c0b7bc67
@@ -8,7 +8,7 @@ module Sequent
8
8
  module SerializesCommand
9
9
  def command
10
10
  args = Sequent::Core::Oj.strict_load(command_json)
11
- Class.const_get(command_type.to_sym).deserialize_from_json(args)
11
+ Class.const_get(command_type).deserialize_from_json(args)
12
12
  end
13
13
 
14
14
  def command=(command)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ class ArgumentSerializer
8
+ class << self
9
+ def serialize_value(value, enclose_hash: false)
10
+ return value.to_s if value.respond_to?(:matches_attr?)
11
+ return %("#{value}") if value.is_a?(String)
12
+ return serialize_hash(value, enclose_hash: enclose_hash) if value.is_a?(Hash)
13
+
14
+ value
15
+ end
16
+
17
+ private
18
+
19
+ def serialize_hash(hash, enclose_hash:)
20
+ serialized = hash
21
+ .map do |(name, value)|
22
+ "#{name}: #{serialize_value(value, enclose_hash: true)}"
23
+ end
24
+ .join(', ')
25
+
26
+ return "{#{serialized}}" if enclose_hash
27
+
28
+ serialized
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dsl'
4
+ require_relative 'argument_serializer'
5
+ require_relative 'equals'
6
+ require_relative 'not_equals'
7
+ require_relative 'greater_than_equals'
8
+ require_relative 'greater_than'
9
+ require_relative 'less_than_equals'
10
+ require_relative 'less_than'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ module DSL
8
+ def register_matcher(name, matcher_class)
9
+ if respond_to?(name)
10
+ fail ArgumentError, "Cannot register attr matcher because it would overwrite existing method '#{name}'"
11
+ end
12
+
13
+ define_method(name) do |*expected|
14
+ matcher_class.new(*expected)
15
+ end
16
+ end
17
+ end
18
+
19
+ extend DSL
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ Equals = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value == expected_value
10
+ end
11
+
12
+ def to_s
13
+ "eq(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :eq,
23
+ Sequent::Core::Helpers::AttrMatchers::Equals,
24
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ GreaterThan = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value > expected_value
10
+ end
11
+
12
+ def to_s
13
+ "gt(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :gt,
23
+ Sequent::Core::Helpers::AttrMatchers::GreaterThan,
24
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ GreaterThanEquals = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value >= expected_value
10
+ end
11
+
12
+ def to_s
13
+ "gte(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :gte,
23
+ Sequent::Core::Helpers::AttrMatchers::GreaterThanEquals,
24
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ LessThan = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value < expected_value
10
+ end
11
+
12
+ def to_s
13
+ "lt(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :lt,
23
+ Sequent::Core::Helpers::AttrMatchers::LessThan,
24
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ LessThanEquals = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value <= expected_value
10
+ end
11
+
12
+ def to_s
13
+ "lte(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :lte,
23
+ Sequent::Core::Helpers::AttrMatchers::LessThanEquals,
24
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module AttrMatchers
7
+ NotEquals = Struct.new(:expected_value) do
8
+ def matches_attr?(actual_value)
9
+ actual_value != expected_value
10
+ end
11
+
12
+ def to_s
13
+ "neq(#{ArgumentSerializer.serialize_value(expected_value)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::AttrMatchers.register_matcher(
22
+ :neq,
23
+ Sequent::Core::Helpers::AttrMatchers::NotEquals,
24
+ )
@@ -40,19 +40,19 @@ module Sequent
40
40
 
41
41
  # module containing class methods to be added
42
42
  module ClassMethods
43
- def types
44
- @types ||= {}
45
- return @merged_types if @merged_types
43
+ attr_reader :types
46
44
 
47
- @merged_types = is_a?(Class) && superclass.respond_to?(:types) ? @types.merge(superclass.types) : @types
48
- included_modules.select { |m| m.include? Sequent::Core::Helpers::AttributeSupport }.each do |mod|
49
- @merged_types.merge!(mod.types)
50
- end
51
- @merged_types
45
+ # Called when this module is included or when a class which includes this module is inherited from.
46
+ #
47
+ # All declared attrs are merged into @types in order to prevent superfluous calculation of types in a class
48
+ # hierarchy.
49
+ def initialize_types
50
+ @types = inherited_types
52
51
  end
53
52
 
54
53
  def attrs(args)
55
- @types ||= {}
54
+ validate_attrs!(args)
55
+
56
56
  @types.merge!(args)
57
57
  associations = []
58
58
  args.each do |attribute, type|
@@ -126,6 +126,18 @@ EOS
126
126
  @upcasters.push(block)
127
127
  end
128
128
 
129
+ private
130
+
131
+ def inherited_types
132
+ merged_types = is_a?(Class) && superclass.respond_to?(:types) ? superclass.types.dup : {}
133
+
134
+ included_modules
135
+ .select { |m| m.include? Sequent::Core::Helpers::AttributeSupport }
136
+ .reduce(merged_types) do |memo, mod|
137
+ memo.merge(mod.types)
138
+ end
139
+ end
140
+
129
141
  def upcast!(hash)
130
142
  return if @upcasters.nil?
131
143
 
@@ -133,11 +145,24 @@ EOS
133
145
  upcaster.call(hash)
134
146
  end
135
147
  end
148
+
149
+ def validate_attrs!(args)
150
+ duplicate_attrs = types.keys & args.keys
151
+
152
+ fail ArgumentError, "Attributes already defined: #{duplicate_attrs.join(', ')}" if duplicate_attrs.any?
153
+ end
154
+
155
+ def inherited(subclass)
156
+ super
157
+
158
+ subclass.initialize_types
159
+ end
136
160
  end
137
161
 
138
162
  # extend host class with class methods when we're included
139
163
  def self.included(host_class)
140
164
  host_class.extend(ClassMethods)
165
+ host_class.initialize_types
141
166
  end
142
167
 
143
168
  def attributes
@@ -36,11 +36,11 @@ module Sequent
36
36
  event_class.types.keys.reject { |k| @@autoset_ignore_attributes.include?(k.to_s) }
37
37
  end
38
38
 
39
- def autoset_attributes_for_events(*event_classes)
40
- event_classes.each do |event_class|
41
- on event_class do |event|
42
- self.class.event_attribute_keys(event_class).each do |attribute_name|
43
- instance_variable_set(:"@#{attribute_name}", event.send(attribute_name.to_sym))
39
+ def autoset_attributes_for_events(*args)
40
+ args.each do |arg|
41
+ on arg do |event|
42
+ self.class.event_attribute_keys(event.class).each do |attribute_name|
43
+ instance_variable_set(:"@#{attribute_name}", event.public_send(attribute_name.to_sym))
44
44
  end
45
45
  end
46
46
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ class MessageDispatcher
7
+ def initialize(message_router, context)
8
+ @message_router = message_router
9
+ @context = context
10
+ end
11
+
12
+ def dispatch_message(message)
13
+ @message_router
14
+ .match_message(message)
15
+ .each { |handler| @context.instance_exec(message, &handler) }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'message_handler_option_registry'
4
+ require_relative 'message_router'
5
+ require_relative 'message_dispatcher'
6
+
3
7
  module Sequent
4
8
  module Core
5
9
  module Helpers
@@ -36,29 +40,79 @@ module Sequent
36
40
  #
37
41
  module MessageHandler
38
42
  module ClassMethods
39
- def on(*message_classes, &block)
40
- message_classes.each do |message_class|
41
- message_mapping[message_class] ||= []
42
- message_mapping[message_class] << block
43
+ def on(*args, **opts, &block)
44
+ OnArgumentsValidator.validate_arguments!(*args)
45
+
46
+ message_matchers = args.map { |arg| MessageMatchers::ArgumentCoercer.coerce_argument(arg) }
47
+
48
+ message_router.register_matchers(
49
+ *message_matchers,
50
+ block,
51
+ )
52
+
53
+ opts.each do |name, value|
54
+ option_registry.call_option(self, name, message_matchers, value)
43
55
  end
44
56
  end
45
57
 
58
+ def option(name, &block)
59
+ option_registry.register_option(name, block)
60
+ end
61
+
46
62
  def message_mapping
47
- @message_mapping ||= {}
63
+ message_router
64
+ .routes
65
+ .select { |matcher, _handlers| matcher.is_a?(MessageMatchers::InstanceOf) }
66
+ .map { |k, v| [k.expected_class, v] }
67
+ .to_h
48
68
  end
49
69
 
50
70
  def handles_message?(message)
51
- message_mapping.keys.include? message.class
71
+ message_router.matches_message?(message)
72
+ end
73
+
74
+ def message_router
75
+ @message_router ||= MessageRouter.new
76
+ end
77
+ end
78
+
79
+ class OnArgumentsValidator
80
+ class << self
81
+ def validate_arguments!(*args)
82
+ fail ArgumentError, "Must provide at least one argument to 'on'" if args.empty?
83
+
84
+ duplicates = args
85
+ .select { |arg| args.count(arg) > 1 }
86
+ .uniq
87
+
88
+ if duplicates.any?
89
+ humanized_duplicates = duplicates
90
+ .map { |x| MessageMatchers::ArgumentSerializer.serialize_value(x) }
91
+ .join(', ')
92
+
93
+ fail ArgumentError,
94
+ "Arguments to 'on' must be unique, duplicates: #{humanized_duplicates}"
95
+ end
96
+ end
52
97
  end
53
98
  end
54
99
 
55
100
  def self.included(host_class)
56
101
  host_class.extend(ClassMethods)
102
+ host_class.extend(MessageMatchers)
103
+ host_class.extend(AttrMatchers)
104
+
105
+ host_class.class_attribute :option_registry, default: MessageHandlerOptionRegistry.new
57
106
  end
58
107
 
59
108
  def handle_message(message)
60
- handlers = self.class.message_mapping[message.class]
61
- handlers&.each { |handler| instance_exec(message, &handler) }
109
+ message_dispatcher.dispatch_message(message)
110
+ end
111
+
112
+ private
113
+
114
+ def message_dispatcher
115
+ MessageDispatcher.new(self.class.message_router, self)
62
116
  end
63
117
  end
64
118
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ class MessageHandlerOptionRegistry
7
+ attr_reader :entries
8
+
9
+ def initialize
10
+ clear_options
11
+ end
12
+
13
+ ##
14
+ # Registers a handler for the given option.
15
+ #
16
+ def register_option(name, handler)
17
+ fail ArgumentError, "Option with name '#{name}' already registered" if option_registered?(name)
18
+
19
+ @entries[name] = handler
20
+ end
21
+
22
+ ##
23
+ # Calls the options with the given arguments with `self` bound to the given context.
24
+ #
25
+ def call_option(context, name, *args)
26
+ handler = find_option(name)
27
+ context.instance_exec(*args, &handler)
28
+ end
29
+
30
+ ##
31
+ # Removes all options from the registry.
32
+ #
33
+ def clear_options
34
+ @entries = {}
35
+ end
36
+
37
+ private
38
+
39
+ ##
40
+ # Returns the handler for given option.
41
+ #
42
+ def find_option(name)
43
+ @entries[name] || fail(
44
+ ArgumentError,
45
+ "Unsupported option: '#{name}'; " \
46
+ "#{@entries.keys.any? ? "registered options: #{@entries.keys.join(', ')}" : 'no registered options'}",
47
+ )
48
+ end
49
+
50
+ ##
51
+ # Returns true when an option for the given name is registered, or false otherwise.
52
+ #
53
+ def option_registered?(name)
54
+ @entries.key?(name)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ Any = Struct.new(:opts) do
8
+ include ExceptOpt
9
+
10
+ def matches_message?(message)
11
+ return false if excluded?(message)
12
+
13
+ true
14
+ end
15
+
16
+ def to_s
17
+ "any#{matcher_arguments}"
18
+ end
19
+
20
+ private
21
+
22
+ def matcher_arguments
23
+ "(except: #{except})" if except
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Sequent::Core::Helpers::MessageMatchers.register_matcher(
32
+ :any,
33
+ Sequent::Core::Helpers::MessageMatchers::Any,
34
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ class ArgumentCoercer
8
+ class << self
9
+ def coerce_argument(arg)
10
+ fail ArgumentError, 'Cannot coerce nil argument' if arg.nil?
11
+
12
+ return MessageMatchers::InstanceOf.new(arg) if [Class, Module].include?(arg.class)
13
+ return arg if arg.respond_to?(:matches_message?)
14
+
15
+ fail ArgumentError,
16
+ "Can't coerce argument '#{arg}'; " \
17
+ 'must be either a Class, Module or message matcher (respond to :matches_message?)'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ class ArgumentSerializer
8
+ class << self
9
+ def serialize_value(value)
10
+ return value.to_s if value.respond_to?(:matches_message?)
11
+ return %("#{value}") if value.is_a?(String)
12
+
13
+ value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ module DSL
8
+ def register_matcher(name, matcher_class)
9
+ if respond_to?(name)
10
+ fail ArgumentError, "Cannot register message matcher because it would overwrite existing method '#{name}'"
11
+ end
12
+
13
+ define_method(name) do |*expected|
14
+ matcher_class.new(*expected)
15
+ end
16
+ end
17
+ end
18
+
19
+ extend DSL
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ module ExceptOpt
8
+ private
9
+
10
+ def excluded?(message)
11
+ [except]
12
+ .flatten
13
+ .compact
14
+ .any? { |x| message.is_a?(x) }
15
+ end
16
+
17
+ def except
18
+ opts.try(:[], :except)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ HasAttrs = Struct.new(:message_matcher, :expected_attrs) do
8
+ def initialize(message_matcher, expected_attrs)
9
+ super
10
+
11
+ fail ArgumentError, 'Missing required message matcher' if message_matcher.nil?
12
+ fail ArgumentError, 'Missing required expected attrs' if expected_attrs.blank?
13
+
14
+ self.message_matcher = ArgumentCoercer.coerce_argument(message_matcher)
15
+ end
16
+
17
+ def matches_message?(message)
18
+ message_matcher.matches_message?(message) &&
19
+ matches_attrs?(message, expected_attrs)
20
+ end
21
+
22
+ def to_s
23
+ 'has_attrs(' \
24
+ "#{MessageMatchers::ArgumentSerializer.serialize_value(message_matcher)}, " \
25
+ "#{AttrMatchers::ArgumentSerializer.serialize_value(expected_attrs)}" \
26
+ ')'
27
+ end
28
+
29
+ private
30
+
31
+ def matches_attrs?(message, expected_attrs, path = [])
32
+ expected_attrs.all? do |(name, expected_value)|
33
+ matches_attr?(message, expected_value, path.dup.push(name))
34
+ end
35
+ end
36
+
37
+ def matches_attr?(message, expected_value, path)
38
+ return matches_attrs?(message, expected_value, path) if expected_value.is_a?(Hash)
39
+
40
+ expected_value = AttrMatchers::Equals.new(expected_value) unless expected_value.respond_to?(:matches_attr?)
41
+ value = path.reduce(message) { |memo, p| memo.public_send(p) }
42
+
43
+ expected_value.matches_attr?(value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Sequent::Core::Helpers::MessageMatchers.register_matcher(
52
+ :has_attrs,
53
+ Sequent::Core::Helpers::MessageMatchers::HasAttrs,
54
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ InstanceOf = Struct.new(:expected_class) do
8
+ def matches_message?(message)
9
+ message.instance_of?(expected_class)
10
+ end
11
+
12
+ def to_s
13
+ expected_class
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Sequent::Core::Helpers::MessageMatchers.register_matcher(
22
+ :instance_of,
23
+ Sequent::Core::Helpers::MessageMatchers::InstanceOf,
24
+ )
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module MessageMatchers
7
+ IsA = Struct.new(:expected_class, :opts) do
8
+ include ExceptOpt
9
+
10
+ def matches_message?(message)
11
+ message.is_a?(expected_class) unless excluded?(message)
12
+ end
13
+
14
+ def to_s
15
+ "is_a(#{matcher_arguments})"
16
+ end
17
+
18
+ private
19
+
20
+ def matcher_arguments
21
+ arguments = expected_class.to_s
22
+ arguments += ", except: #{except}" if except
23
+ arguments
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Sequent::Core::Helpers::MessageMatchers.register_matcher(
32
+ :is_a,
33
+ Sequent::Core::Helpers::MessageMatchers::IsA,
34
+ )
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dsl'
4
+ require_relative 'argument_coercer'
5
+ require_relative 'argument_serializer'
6
+ require_relative 'except_opt'
7
+ require_relative 'instance_of'
8
+ require_relative 'is_a'
9
+ require_relative 'has_attrs'
10
+ require_relative 'any'
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './attr_matchers/attr_matchers'
4
+ require_relative './message_matchers/message_matchers'
5
+
6
+ module Sequent
7
+ module Core
8
+ module Helpers
9
+ class MessageRouter
10
+ attr_reader :routes
11
+
12
+ def initialize
13
+ clear_routes
14
+ end
15
+
16
+ ##
17
+ # Registers a handler for the given matchers.
18
+ #
19
+ # A matcher must implement #matches_message?(message) and return a truthy value when it matches,
20
+ # or a falsey value otherwise.
21
+ #
22
+ def register_matchers(*matchers, handler)
23
+ matchers.each do |matcher|
24
+ @routes[matcher] << handler
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Returns a set of handlers that match the given message, or an empty set when none match.
30
+ #
31
+ def match_message(message)
32
+ @routes
33
+ .reduce(Set.new) do |memo, (matcher, handlers)|
34
+ memo = memo.merge(handlers) if matcher.matches_message?(message)
35
+ memo
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Returns true when there is at least one handler for the given message, or false otherwise.
41
+ #
42
+ def matches_message?(message)
43
+ match_message(message).any?
44
+ end
45
+
46
+ ##
47
+ # Removes all routes from the router.
48
+ #
49
+ def clear_routes
50
+ @routes = Hash.new { |h, k| h[k] = Set.new }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -13,7 +13,7 @@ module Sequent
13
13
  Workflows << subclass
14
14
  end
15
15
 
16
- def self.on(*message_classes, &block)
16
+ def self.on(*args, **opts, &block)
17
17
  decorated_block = ->(event) do
18
18
  begin
19
19
  old_event = CurrentEvent.current
@@ -23,7 +23,7 @@ module Sequent
23
23
  CurrentEvent.current = old_event
24
24
  end
25
25
  end
26
- super(*message_classes, &decorated_block)
26
+ super(*args, **opts, &decorated_block)
27
27
  end
28
28
 
29
29
  def execute_commands(*commands)
@@ -11,8 +11,8 @@ module Sequent
11
11
  # This provides a nice DSL for event based testing of your CommandHandler like
12
12
  #
13
13
  # given_events InvoiceCreatedEvent.new(args)
14
- # when_command PayInvoiceCommand(args)
15
- # then_events InvoicePaidEvent(args)
14
+ # when_command PayInvoiceCommand.new(args)
15
+ # then_events InvoicePaidEvent.new(args)
16
16
  #
17
17
  # Example for Rspec config
18
18
  #
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sequent
4
- VERSION = '4.3.0'
4
+ VERSION = '5.0.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequent
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Vonk
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2022-03-21 00:00:00.000000000 Z
15
+ date: 2022-06-16 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activemodel
@@ -21,9 +21,9 @@ dependencies:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
23
  version: '5.0'
24
- - - "<="
24
+ - - "<"
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.2
26
+ version: '7.1'
27
27
  type: :runtime
28
28
  prerelease: false
29
29
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,9 +31,9 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '5.0'
34
- - - "<="
34
+ - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: 7.0.2
36
+ version: '7.1'
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: activerecord
39
39
  requirement: !ruby/object:Gem::Requirement
@@ -41,9 +41,9 @@ dependencies:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  version: '5.0'
44
- - - "<="
44
+ - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: 7.0.2
46
+ version: '7.1'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,9 +51,9 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '5.0'
54
- - - "<="
54
+ - - "<"
55
55
  - !ruby/object:Gem::Version
56
- version: 7.0.2
56
+ version: '7.1'
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: bcrypt
59
59
  requirement: !ruby/object:Gem::Requirement
@@ -333,6 +333,15 @@ files:
333
333
  - lib/sequent/core/ext/ext.rb
334
334
  - lib/sequent/core/helpers/array_with_type.rb
335
335
  - lib/sequent/core/helpers/association_validator.rb
336
+ - lib/sequent/core/helpers/attr_matchers/argument_serializer.rb
337
+ - lib/sequent/core/helpers/attr_matchers/attr_matchers.rb
338
+ - lib/sequent/core/helpers/attr_matchers/dsl.rb
339
+ - lib/sequent/core/helpers/attr_matchers/equals.rb
340
+ - lib/sequent/core/helpers/attr_matchers/greater_than.rb
341
+ - lib/sequent/core/helpers/attr_matchers/greater_than_equals.rb
342
+ - lib/sequent/core/helpers/attr_matchers/less_than.rb
343
+ - lib/sequent/core/helpers/attr_matchers/less_than_equals.rb
344
+ - lib/sequent/core/helpers/attr_matchers/not_equals.rb
336
345
  - lib/sequent/core/helpers/attribute_support.rb
337
346
  - lib/sequent/core/helpers/autoset_attributes.rb
338
347
  - lib/sequent/core/helpers/boolean_validator.rb
@@ -343,7 +352,19 @@ files:
343
352
  - lib/sequent/core/helpers/equal_support.rb
344
353
  - lib/sequent/core/helpers/helpers.rb
345
354
  - lib/sequent/core/helpers/mergable.rb
355
+ - lib/sequent/core/helpers/message_dispatcher.rb
346
356
  - lib/sequent/core/helpers/message_handler.rb
357
+ - lib/sequent/core/helpers/message_handler_option_registry.rb
358
+ - lib/sequent/core/helpers/message_matchers/any.rb
359
+ - lib/sequent/core/helpers/message_matchers/argument_coercer.rb
360
+ - lib/sequent/core/helpers/message_matchers/argument_serializer.rb
361
+ - lib/sequent/core/helpers/message_matchers/dsl.rb
362
+ - lib/sequent/core/helpers/message_matchers/except_opt.rb
363
+ - lib/sequent/core/helpers/message_matchers/has_attrs.rb
364
+ - lib/sequent/core/helpers/message_matchers/instance_of.rb
365
+ - lib/sequent/core/helpers/message_matchers/is_a.rb
366
+ - lib/sequent/core/helpers/message_matchers/message_matchers.rb
367
+ - lib/sequent/core/helpers/message_router.rb
347
368
  - lib/sequent/core/helpers/param_support.rb
348
369
  - lib/sequent/core/helpers/secret.rb
349
370
  - lib/sequent/core/helpers/string_support.rb