sequent 4.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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