sequent 4.3.0 → 6.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/bin/sequent +1 -1
- data/db/sequent_schema.rb +3 -3
- data/lib/sequent/configuration.rb +19 -1
- data/lib/sequent/core/aggregate_root.rb +2 -6
- data/lib/sequent/core/aggregate_roots.rb +2 -6
- data/lib/sequent/core/command.rb +8 -12
- data/lib/sequent/core/command_record.rb +1 -1
- data/lib/sequent/core/command_service.rb +13 -2
- data/lib/sequent/core/core.rb +1 -0
- data/lib/sequent/core/event.rb +2 -2
- data/lib/sequent/core/event_store.rb +15 -2
- data/lib/sequent/core/ext/ext.rb +17 -0
- 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 +35 -9
- data/lib/sequent/core/helpers/autoset_attributes.rb +5 -5
- data/lib/sequent/core/helpers/default_validators.rb +3 -0
- 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/helpers/param_support.rb +2 -0
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +5 -0
- data/lib/sequent/core/helpers/time_validator.rb +23 -0
- data/lib/sequent/core/helpers/value_validators.rb +11 -0
- data/lib/sequent/core/middleware/chain.rb +37 -0
- data/lib/sequent/core/middleware/middleware.rb +3 -0
- data/lib/sequent/core/projector.rb +3 -11
- data/lib/sequent/core/workflow.rb +5 -13
- data/lib/sequent/generator/template_project/Rakefile +2 -2
- data/lib/sequent/generator/template_project/db/sequent_schema.rb +3 -3
- data/lib/sequent/generator/template_project/spec/spec_helper.rb +1 -1
- data/lib/sequent/migrations/migrations.rb +1 -0
- data/lib/sequent/migrations/projectors.rb +2 -2
- data/lib/sequent/migrations/sequent_schema.rb +40 -0
- data/lib/sequent/migrations/view_schema.rb +39 -3
- data/lib/sequent/rake/migration_tasks.rb +36 -33
- data/lib/sequent/support/database.rb +29 -13
- data/lib/sequent/test/command_handler_helpers.rb +3 -3
- data/lib/sequent/test/database_helpers.rb +20 -0
- data/lib/sequent/test/time_comparison.rb +2 -5
- data/lib/sequent/test/{event_handler_helpers.rb → workflow_helpers.rb} +24 -10
- data/lib/sequent/test.rb +2 -1
- data/lib/sequent/util/dry_run.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +40 -15
- data/lib/sequent/rake/tasks.rb +0 -121
@@ -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
|
@@ -16,6 +16,7 @@ module Sequent
|
|
16
16
|
::Boolean => ->(value) { parse_to_bool(value) },
|
17
17
|
::Date => ->(value) { parse_to_date(value) },
|
18
18
|
::DateTime => ->(value) { parse_to_date_time(value) },
|
19
|
+
::Time => ->(value) { parse_to_time(value) },
|
19
20
|
::Sequent::Core::Helpers::ArrayWithType => ->(values, type_in_array) { parse_array(values, type_in_array) },
|
20
21
|
::Sequent::Core::Helpers::Secret => ->(value) { Sequent::Core::Helpers::Secret.new(value).encrypt },
|
21
22
|
}.freeze
|
@@ -52,6 +53,10 @@ module Sequent
|
|
52
53
|
value.is_a?(DateTime) ? value : DateTime.deserialize_from_json(value)
|
53
54
|
end
|
54
55
|
|
56
|
+
def self.parse_to_time(value)
|
57
|
+
value.is_a?(Time) ? value : Time.deserialize_from_json(value)
|
58
|
+
end
|
59
|
+
|
55
60
|
def self.parse_array(values, type_in_array)
|
56
61
|
fail "invalid value for array(): \"#{values}\"" unless values.is_a?(Array)
|
57
62
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module Sequent
|
6
|
+
module Core
|
7
|
+
module Helpers
|
8
|
+
# Validates Time
|
9
|
+
# Automatically included when using a
|
10
|
+
#
|
11
|
+
# attrs value: Time
|
12
|
+
class TimeValidator < ActiveModel::EachValidator
|
13
|
+
def validate_each(subject, attribute, value)
|
14
|
+
return if value.is_a?(Time)
|
15
|
+
|
16
|
+
Time.deserialize_from_json(value)
|
17
|
+
rescue StandardError
|
18
|
+
subject.errors.add attribute, :invalid_time
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -16,6 +16,7 @@ module Sequent
|
|
16
16
|
::Integer => ->(value) { valid_integer?(value) },
|
17
17
|
::Boolean => ->(value) { valid_bool?(value) },
|
18
18
|
::Date => ->(value) { valid_date?(value) },
|
19
|
+
::Time => ->(value) { valid_time?(value) },
|
19
20
|
::DateTime => ->(value) { valid_date_time?(value) },
|
20
21
|
}.freeze
|
21
22
|
|
@@ -53,6 +54,16 @@ module Sequent
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
57
|
+
def self.valid_time?(value)
|
58
|
+
return true if value.blank?
|
59
|
+
|
60
|
+
begin
|
61
|
+
value.is_a?(Time) || !!Time.iso8601(value.dup)
|
62
|
+
rescue StandardError
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
56
67
|
def self.valid_string?(value)
|
57
68
|
return true if value.nil?
|
58
69
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Core
|
5
|
+
module Middleware
|
6
|
+
class Chain
|
7
|
+
attr_reader :entries
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
clear
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(middleware)
|
14
|
+
@entries.push(middleware)
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
@entries = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def invoke(*args, &invoker)
|
22
|
+
chain = @entries.dup
|
23
|
+
|
24
|
+
traverse_chain = -> do
|
25
|
+
if chain.empty?
|
26
|
+
invoker.call
|
27
|
+
else
|
28
|
+
chain.shift.call(*args, &traverse_chain)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
traverse_chain.call
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -86,17 +86,13 @@ module Sequent
|
|
86
86
|
extend Forwardable
|
87
87
|
include Helpers::MessageHandler
|
88
88
|
include Migratable
|
89
|
+
extend ActiveSupport::DescendantsTracker
|
89
90
|
|
90
91
|
def initialize(persistor = Sequent::Core::Persistors::ActiveRecordPersistor.new)
|
91
92
|
ensure_valid!
|
92
93
|
@persistor = persistor
|
93
94
|
end
|
94
95
|
|
95
|
-
def self.inherited(subclass)
|
96
|
-
super
|
97
|
-
Projectors << subclass
|
98
|
-
end
|
99
|
-
|
100
96
|
def self.replay_persistor
|
101
97
|
nil
|
102
98
|
end
|
@@ -132,22 +128,18 @@ module Sequent
|
|
132
128
|
end
|
133
129
|
|
134
130
|
#
|
135
|
-
# Utility class containing all subclasses of Projector
|
131
|
+
# Utility class containing all subclasses of Projector.
|
136
132
|
#
|
137
133
|
class Projectors
|
138
134
|
class << self
|
139
135
|
def projectors
|
140
|
-
|
136
|
+
Sequent::Projector.descendants
|
141
137
|
end
|
142
138
|
|
143
139
|
def all
|
144
140
|
projectors
|
145
141
|
end
|
146
142
|
|
147
|
-
def <<(projector)
|
148
|
-
projectors << projector
|
149
|
-
end
|
150
|
-
|
151
143
|
def find(projector_name)
|
152
144
|
projectors.find { |c| c.name == projector_name }
|
153
145
|
end
|