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.
- checksums.yaml +4 -4
- data/lib/sequent/core/command_record.rb +1 -1
- data/lib/sequent/core/helpers/attr_matchers/argument_serializer.rb +35 -0
- data/lib/sequent/core/helpers/attr_matchers/attr_matchers.rb +10 -0
- data/lib/sequent/core/helpers/attr_matchers/dsl.rb +23 -0
- data/lib/sequent/core/helpers/attr_matchers/equals.rb +24 -0
- data/lib/sequent/core/helpers/attr_matchers/greater_than.rb +24 -0
- data/lib/sequent/core/helpers/attr_matchers/greater_than_equals.rb +24 -0
- data/lib/sequent/core/helpers/attr_matchers/less_than.rb +24 -0
- data/lib/sequent/core/helpers/attr_matchers/less_than_equals.rb +24 -0
- data/lib/sequent/core/helpers/attr_matchers/not_equals.rb +24 -0
- data/lib/sequent/core/helpers/attribute_support.rb +34 -9
- data/lib/sequent/core/helpers/autoset_attributes.rb +5 -5
- data/lib/sequent/core/helpers/message_dispatcher.rb +20 -0
- data/lib/sequent/core/helpers/message_handler.rb +62 -8
- data/lib/sequent/core/helpers/message_handler_option_registry.rb +59 -0
- data/lib/sequent/core/helpers/message_matchers/any.rb +34 -0
- data/lib/sequent/core/helpers/message_matchers/argument_coercer.rb +24 -0
- data/lib/sequent/core/helpers/message_matchers/argument_serializer.rb +20 -0
- data/lib/sequent/core/helpers/message_matchers/dsl.rb +23 -0
- data/lib/sequent/core/helpers/message_matchers/except_opt.rb +24 -0
- data/lib/sequent/core/helpers/message_matchers/has_attrs.rb +54 -0
- data/lib/sequent/core/helpers/message_matchers/instance_of.rb +24 -0
- data/lib/sequent/core/helpers/message_matchers/is_a.rb +34 -0
- data/lib/sequent/core/helpers/message_matchers/message_matchers.rb +10 -0
- data/lib/sequent/core/helpers/message_router.rb +55 -0
- data/lib/sequent/core/workflow.rb +2 -2
- data/lib/sequent/test/command_handler_helpers.rb +2 -2
- data/lib/version.rb +1 -1
- metadata +31 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1dea4b53205452eaa68007ed47688b3fa074229677186e9342265cdd7a31d4db
|
4
|
+
data.tar.gz: 2d342b9a013372ca99b441c4980432f642a56741746291d814c68ad60cf6c26e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
44
|
-
@types ||= {}
|
45
|
-
return @merged_types if @merged_types
|
43
|
+
attr_reader :types
|
46
44
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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(*
|
40
|
-
|
41
|
-
on
|
42
|
-
self.class.event_attribute_keys(
|
43
|
-
instance_variable_set(:"@#{attribute_name}", event.
|
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(*
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
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(*
|
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(*
|
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
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
|
+
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-
|
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.
|
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.
|
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.
|
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.
|
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
|