sequent 4.0.0 → 4.1.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 +31 -25
- data/lib/notices.rb +2 -0
- data/lib/sequent/application_record.rb +2 -0
- data/lib/sequent/configuration.rb +24 -31
- data/lib/sequent/core/aggregate_repository.rb +17 -13
- data/lib/sequent/core/aggregate_root.rb +16 -7
- data/lib/sequent/core/aggregate_roots.rb +24 -0
- data/lib/sequent/core/aggregate_snapshotter.rb +8 -5
- data/lib/sequent/core/base_command_handler.rb +4 -2
- data/lib/sequent/core/command.rb +17 -9
- data/lib/sequent/core/command_record.rb +8 -3
- data/lib/sequent/core/command_service.rb +18 -18
- data/lib/sequent/core/core.rb +2 -0
- data/lib/sequent/core/current_event.rb +2 -0
- data/lib/sequent/core/event.rb +16 -11
- data/lib/sequent/core/event_publisher.rb +16 -15
- data/lib/sequent/core/event_record.rb +7 -7
- data/lib/sequent/core/event_store.rb +57 -50
- data/lib/sequent/core/ext/ext.rb +9 -1
- data/lib/sequent/core/helpers/array_with_type.rb +4 -1
- data/lib/sequent/core/helpers/association_validator.rb +9 -7
- data/lib/sequent/core/helpers/attribute_support.rb +45 -28
- data/lib/sequent/core/helpers/autoset_attributes.rb +4 -4
- data/lib/sequent/core/helpers/boolean_validator.rb +6 -1
- data/lib/sequent/core/helpers/copyable.rb +2 -2
- data/lib/sequent/core/helpers/date_time_validator.rb +4 -1
- data/lib/sequent/core/helpers/date_validator.rb +6 -1
- data/lib/sequent/core/helpers/default_validators.rb +12 -10
- data/lib/sequent/core/helpers/equal_support.rb +8 -6
- data/lib/sequent/core/helpers/helpers.rb +2 -0
- data/lib/sequent/core/helpers/mergable.rb +6 -5
- data/lib/sequent/core/helpers/message_handler.rb +3 -1
- data/lib/sequent/core/helpers/param_support.rb +19 -15
- data/lib/sequent/core/helpers/secret.rb +14 -12
- data/lib/sequent/core/helpers/string_support.rb +5 -4
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +7 -2
- data/lib/sequent/core/helpers/string_validator.rb +6 -1
- data/lib/sequent/core/helpers/type_conversion_support.rb +5 -3
- data/lib/sequent/core/helpers/uuid_helper.rb +5 -2
- data/lib/sequent/core/helpers/value_validators.rb +23 -9
- data/lib/sequent/core/persistors/active_record_persistor.rb +19 -9
- data/lib/sequent/core/persistors/persistor.rb +16 -14
- data/lib/sequent/core/persistors/persistors.rb +2 -0
- data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +70 -47
- data/lib/sequent/core/projector.rb +25 -22
- data/lib/sequent/core/random_uuid_generator.rb +2 -0
- data/lib/sequent/core/sequent_oj.rb +2 -0
- data/lib/sequent/core/stream_record.rb +9 -3
- data/lib/sequent/core/transactions/active_record_transaction_provider.rb +5 -9
- data/lib/sequent/core/transactions/no_transactions.rb +2 -1
- data/lib/sequent/core/transactions/transactions.rb +2 -0
- data/lib/sequent/core/value_object.rb +8 -10
- data/lib/sequent/core/workflow.rb +7 -5
- data/lib/sequent/generator/aggregate.rb +16 -10
- data/lib/sequent/generator/command.rb +26 -19
- data/lib/sequent/generator/event.rb +19 -17
- data/lib/sequent/generator/generator.rb +2 -0
- data/lib/sequent/generator/project.rb +2 -0
- data/lib/sequent/generator/template_project/Gemfile +1 -1
- data/lib/sequent/generator.rb +2 -0
- data/lib/sequent/migrations/executor.rb +22 -13
- data/lib/sequent/migrations/functions.rb +5 -6
- data/lib/sequent/migrations/migrate_events.rb +12 -9
- data/lib/sequent/migrations/migrations.rb +2 -1
- data/lib/sequent/migrations/planner.rb +33 -23
- data/lib/sequent/migrations/projectors.rb +4 -3
- data/lib/sequent/migrations/sql.rb +2 -0
- data/lib/sequent/migrations/view_schema.rb +84 -45
- data/lib/sequent/rake/migration_tasks.rb +58 -22
- data/lib/sequent/rake/tasks.rb +5 -2
- data/lib/sequent/sequent.rb +2 -0
- data/lib/sequent/support/database.rb +30 -15
- data/lib/sequent/support/view_projection.rb +6 -3
- data/lib/sequent/support/view_schema.rb +2 -0
- data/lib/sequent/support.rb +2 -0
- data/lib/sequent/test/command_handler_helpers.rb +35 -17
- data/lib/sequent/test/event_handler_helpers.rb +10 -4
- data/lib/sequent/test/event_stream_helpers.rb +7 -3
- data/lib/sequent/test/time_comparison.rb +12 -5
- data/lib/sequent/test.rb +2 -0
- data/lib/sequent/util/dry_run.rb +11 -8
- data/lib/sequent/util/printer.rb +6 -5
- data/lib/sequent/util/skip_if_already_processing.rb +3 -1
- data/lib/sequent/util/timer.rb +2 -0
- data/lib/sequent/util/util.rb +2 -0
- data/lib/sequent.rb +2 -0
- data/lib/version.rb +3 -1
- metadata +81 -66
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
require_relative 'value_validators'
|
|
3
5
|
|
|
@@ -18,7 +20,10 @@ module Sequent
|
|
|
18
20
|
# They will be converted to `true`, `false` or `nil`
|
|
19
21
|
class BooleanValidator < ActiveModel::EachValidator
|
|
20
22
|
def validate_each(subject, attribute, value)
|
|
21
|
-
|
|
23
|
+
unless Sequent::Core::Helpers::ValueValidators.for(Boolean).valid_value?(value)
|
|
24
|
+
subject.errors.add attribute,
|
|
25
|
+
:invalid_boolean
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -10,8 +12,9 @@ module Sequent
|
|
|
10
12
|
class DateTimeValidator < ActiveModel::EachValidator
|
|
11
13
|
def validate_each(subject, attribute, value)
|
|
12
14
|
return if value.is_a?(DateTime)
|
|
15
|
+
|
|
13
16
|
DateTime.deserialize_from_json(value)
|
|
14
|
-
rescue
|
|
17
|
+
rescue StandardError
|
|
15
18
|
subject.errors.add attribute, :invalid_date_time
|
|
16
19
|
end
|
|
17
20
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
require_relative 'value_validators'
|
|
3
5
|
|
|
@@ -10,7 +12,10 @@ module Sequent
|
|
|
10
12
|
# attrs value: Date
|
|
11
13
|
class DateValidator < ActiveModel::EachValidator
|
|
12
14
|
def validate_each(subject, attribute, value)
|
|
13
|
-
|
|
15
|
+
unless Sequent::Core::Helpers::ValueValidators.for(Date).valid_value?(value)
|
|
16
|
+
subject.errors.add attribute,
|
|
17
|
+
:invalid_date
|
|
18
|
+
end
|
|
14
19
|
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'string_validator'
|
|
2
4
|
require_relative 'boolean_validator'
|
|
3
5
|
require_relative 'date_time_validator'
|
|
@@ -13,18 +15,18 @@ module Sequent
|
|
|
13
15
|
klass.validates_numericality_of field, only_integer: true, allow_nil: true, allow_blank: true
|
|
14
16
|
end,
|
|
15
17
|
Date => ->(klass, field) do
|
|
16
|
-
klass.validates field,
|
|
18
|
+
klass.validates field, 'sequent::Core::Helpers::Date' => true
|
|
17
19
|
end,
|
|
18
20
|
DateTime => ->(klass, field) do
|
|
19
|
-
klass.validates field,
|
|
21
|
+
klass.validates field, 'sequent::Core::Helpers::DateTime' => true
|
|
20
22
|
end,
|
|
21
|
-
Boolean => ->
|
|
22
|
-
klass.validates field,
|
|
23
|
+
Boolean => ->(klass, field) do
|
|
24
|
+
klass.validates field, 'sequent::Core::Helpers::Boolean' => true
|
|
23
25
|
end,
|
|
24
|
-
String => ->
|
|
25
|
-
klass.validates field,
|
|
26
|
+
String => ->(klass, field) do
|
|
27
|
+
klass.validates field, 'sequent::Core::Helpers::String' => true
|
|
26
28
|
end,
|
|
27
|
-
Sequent::Core::Helpers::Secret => ->
|
|
29
|
+
Sequent::Core::Helpers::Secret => ->(klass, field) do
|
|
28
30
|
klass.after_validation do |object|
|
|
29
31
|
if object.errors&.any?
|
|
30
32
|
object.send("#{field}=", nil)
|
|
@@ -33,8 +35,8 @@ module Sequent
|
|
|
33
35
|
object.send("#{field}=", Sequent::Secret.new(raw_value)) if raw_value
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
|
-
end
|
|
37
|
-
}
|
|
38
|
+
end,
|
|
39
|
+
}.freeze
|
|
38
40
|
|
|
39
41
|
def self.for(type)
|
|
40
42
|
new(type)
|
|
@@ -46,7 +48,7 @@ module Sequent
|
|
|
46
48
|
|
|
47
49
|
def add_validations_for(klass, field)
|
|
48
50
|
validator = VALIDATORS[@type]
|
|
49
|
-
validator
|
|
51
|
+
validator&.call(klass, field)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -8,16 +10,17 @@ module Sequent
|
|
|
8
10
|
#
|
|
9
11
|
module EqualSupport
|
|
10
12
|
def ==(other)
|
|
11
|
-
return false if other
|
|
13
|
+
return false if other.nil?
|
|
12
14
|
return false if self.class != other.class
|
|
15
|
+
|
|
13
16
|
self.class.types.each do |name, _|
|
|
14
|
-
self_value =
|
|
17
|
+
self_value = send(name)
|
|
15
18
|
other_value = other.send(name)
|
|
16
19
|
if self_value.class == DateTime && other_value.class == DateTime
|
|
17
20
|
# we don't care about milliseconds. If you know a better way of checking for equality please improve.
|
|
18
|
-
return false unless
|
|
21
|
+
return false unless self_value.iso8601 == other_value.iso8601
|
|
19
22
|
else
|
|
20
|
-
return false unless
|
|
23
|
+
return false unless self_value == other_value
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
true
|
|
@@ -26,7 +29,7 @@ module Sequent
|
|
|
26
29
|
def hash
|
|
27
30
|
hash = 17
|
|
28
31
|
self.class.types.each do |name, _|
|
|
29
|
-
hash = hash * 31 +
|
|
32
|
+
hash = hash * 31 + send(name).hash
|
|
30
33
|
end
|
|
31
34
|
hash
|
|
32
35
|
end
|
|
@@ -35,7 +38,6 @@ module Sequent
|
|
|
35
38
|
self == other
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
|
-
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -6,17 +8,16 @@ module Sequent
|
|
|
6
8
|
# ben = Person.new(name: 'Ben').merge!(name: 'Ben Vonk')
|
|
7
9
|
#
|
|
8
10
|
module Mergable
|
|
9
|
-
|
|
10
11
|
def merge!(attrs = {})
|
|
11
|
-
warn
|
|
12
|
+
warn <<~EOS
|
|
13
|
+
[DEPRECATION] `merge!` is deprecated. Please use `copy` instead. This method will no longer be included in the next version of Sequent. You can still use it but you will have to include the module `Sequent::Core::Helpers::Mergable` yourself.
|
|
14
|
+
EOS
|
|
12
15
|
attrs.each do |name, value|
|
|
13
|
-
|
|
16
|
+
send("#{name}=", value)
|
|
14
17
|
end
|
|
15
18
|
self
|
|
16
19
|
end
|
|
17
|
-
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
|
-
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -56,7 +58,7 @@ module Sequent
|
|
|
56
58
|
|
|
57
59
|
def handle_message(message)
|
|
58
60
|
handlers = self.class.message_mapping[message.class]
|
|
59
|
-
handlers
|
|
61
|
+
handlers&.each { |handler| instance_exec(message, &handler) }
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -21,7 +23,6 @@ module Sequent
|
|
|
21
23
|
def from_form_data(params = {})
|
|
22
24
|
from_params(params, false)
|
|
23
25
|
end
|
|
24
|
-
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# extend host class with class methods when we're included
|
|
@@ -35,7 +36,8 @@ module Sequent
|
|
|
35
36
|
value = params[attribute]
|
|
36
37
|
|
|
37
38
|
next if strict_nil_check && value.nil?
|
|
38
|
-
next if
|
|
39
|
+
next if !strict_nil_check && value.blank?
|
|
40
|
+
|
|
39
41
|
if type.respond_to? :from_params
|
|
40
42
|
value = type.from_params(value)
|
|
41
43
|
elsif value.is_a?(Array)
|
|
@@ -58,10 +60,12 @@ module Sequent
|
|
|
58
60
|
def as_params
|
|
59
61
|
hash = HashWithIndifferentAccess.new
|
|
60
62
|
self.class.types.each do |field|
|
|
61
|
-
value =
|
|
62
|
-
next if field[0] ==
|
|
63
|
-
|
|
63
|
+
value = instance_variable_get("@#{field[0]}")
|
|
64
|
+
next if field[0] == 'errors'
|
|
65
|
+
|
|
66
|
+
hash[field[0]] = if value.is_a?(Array)
|
|
64
67
|
next if value.blank?
|
|
68
|
+
|
|
65
69
|
value.map { |v| value_to_string(v) }
|
|
66
70
|
else
|
|
67
71
|
value_to_string(value)
|
|
@@ -86,16 +90,16 @@ module Sequent
|
|
|
86
90
|
|
|
87
91
|
def make_params(key, enumerable, memo = {})
|
|
88
92
|
case enumerable
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
when Array
|
|
94
|
+
enumerable.each_with_index do |object, index|
|
|
95
|
+
make_params("#{key}[#{index}]", object, memo)
|
|
96
|
+
end
|
|
97
|
+
when Hash
|
|
98
|
+
enumerable.each do |hash_key, object|
|
|
99
|
+
make_params("#{key}[#{hash_key}]", object, memo)
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
memo[key] = enumerable
|
|
99
103
|
end
|
|
100
104
|
memo
|
|
101
105
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'bcrypt'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
4
6
|
module Core
|
|
5
7
|
module Helpers
|
|
6
|
-
|
|
7
8
|
#
|
|
8
9
|
# You can use this in Commands to handle for instance passwords
|
|
9
10
|
# safely. It uses BCrypt to encrypt the Secret.
|
|
@@ -55,7 +56,6 @@ module Sequent
|
|
|
55
56
|
# See +re_encrypt_secret+
|
|
56
57
|
# See +verify_secret+
|
|
57
58
|
class Secret
|
|
58
|
-
|
|
59
59
|
class << self
|
|
60
60
|
def deserialize_from_json(value)
|
|
61
61
|
new(value)
|
|
@@ -65,7 +65,8 @@ module Sequent
|
|
|
65
65
|
# Creates a hash for the given clear text password.
|
|
66
66
|
#
|
|
67
67
|
def encrypt_secret(clear_text_secret)
|
|
68
|
-
fail ArgumentError
|
|
68
|
+
fail ArgumentError, 'clear_text_secret can not be blank' if clear_text_secret.blank?
|
|
69
|
+
|
|
69
70
|
BCrypt::Password.create(clear_text_secret)
|
|
70
71
|
end
|
|
71
72
|
|
|
@@ -74,8 +75,8 @@ module Sequent
|
|
|
74
75
|
# (essentially re-creating the secret hash).
|
|
75
76
|
#
|
|
76
77
|
def re_encrypt_secret(clear_text_secret, hashed_secret)
|
|
77
|
-
fail ArgumentError
|
|
78
|
-
fail ArgumentError
|
|
78
|
+
fail ArgumentError, 'clear_text_secret can not be blank' if clear_text_secret.blank?
|
|
79
|
+
fail ArgumentError, 'hashed_secret can not be blank' if hashed_secret.blank?
|
|
79
80
|
|
|
80
81
|
BCrypt::Engine.hash_secret(clear_text_secret, hashed_secret)
|
|
81
82
|
end
|
|
@@ -84,7 +85,7 @@ module Sequent
|
|
|
84
85
|
# Verifies that the hashed and clear text secret are equal.
|
|
85
86
|
#
|
|
86
87
|
def verify_secret(hashed_secret, clear_text_secret)
|
|
87
|
-
return false if
|
|
88
|
+
return false if hashed_secret.blank? || clear_text_secret.blank?
|
|
88
89
|
|
|
89
90
|
BCrypt::Password.new(hashed_secret) == clear_text_secret
|
|
90
91
|
end
|
|
@@ -93,12 +94,13 @@ module Sequent
|
|
|
93
94
|
attr_reader :value
|
|
94
95
|
|
|
95
96
|
def initialize(value)
|
|
96
|
-
fail ArgumentError
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
fail ArgumentError, 'value can not be blank' if value.blank?
|
|
98
|
+
|
|
99
|
+
@value = if value.is_a?(Secret)
|
|
100
|
+
value.value
|
|
101
|
+
else
|
|
102
|
+
value
|
|
103
|
+
end
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
def encrypt
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -9,14 +11,13 @@ module Sequent
|
|
|
9
11
|
module StringSupport
|
|
10
12
|
def to_s
|
|
11
13
|
s = "#{self.class.name}: "
|
|
12
|
-
|
|
13
|
-
value =
|
|
14
|
+
instance_variables.each do |name|
|
|
15
|
+
value = instance_variable_get(name.to_s)
|
|
14
16
|
s += "#{name}=[#{value}], "
|
|
15
17
|
end
|
|
16
|
-
|
|
18
|
+
'{' + s.chomp(', ') + '}'
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
|
-
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../ext/ext'
|
|
2
4
|
require_relative 'array_with_type'
|
|
3
5
|
|
|
@@ -16,10 +18,11 @@ module Sequent
|
|
|
16
18
|
::DateTime => ->(value) { parse_to_date_time(value) },
|
|
17
19
|
::Sequent::Core::Helpers::ArrayWithType => ->(values, type_in_array) { parse_array(values, type_in_array) },
|
|
18
20
|
::Sequent::Core::Helpers::Secret => ->(value) { Sequent::Core::Helpers::Secret.new(value).encrypt },
|
|
19
|
-
}
|
|
21
|
+
}.freeze
|
|
20
22
|
|
|
21
23
|
def self.parse_to_integer(value)
|
|
22
24
|
return value if value.is_a?(Integer)
|
|
25
|
+
|
|
23
26
|
Integer(value, 10) unless value.blank?
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -35,12 +38,13 @@ module Sequent
|
|
|
35
38
|
if value.blank? && !(value.is_a?(TrueClass) || value.is_a?(FalseClass))
|
|
36
39
|
nil
|
|
37
40
|
else
|
|
38
|
-
(value.is_a?(TrueClass) || value ==
|
|
41
|
+
(value.is_a?(TrueClass) || value == 'true')
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
def self.parse_to_date(value)
|
|
43
46
|
return if value.blank?
|
|
47
|
+
|
|
44
48
|
value.is_a?(Date) ? value : Date.iso8601(value.dup)
|
|
45
49
|
end
|
|
46
50
|
|
|
@@ -50,6 +54,7 @@ module Sequent
|
|
|
50
54
|
|
|
51
55
|
def self.parse_array(values, type_in_array)
|
|
52
56
|
fail "invalid value for array(): \"#{values}\"" unless values.is_a?(Array)
|
|
57
|
+
|
|
53
58
|
values.map do |item|
|
|
54
59
|
if item.respond_to?(:parse_attrs_to_correct_types)
|
|
55
60
|
item.parse_attrs_to_correct_types
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
require_relative 'value_validators'
|
|
3
5
|
|
|
@@ -16,7 +18,10 @@ module Sequent
|
|
|
16
18
|
#
|
|
17
19
|
class StringValidator < ActiveModel::EachValidator
|
|
18
20
|
def validate_each(subject, attribute, value)
|
|
19
|
-
|
|
21
|
+
unless Sequent::Core::Helpers::ValueValidators.for(String).valid_value?(value)
|
|
22
|
+
subject.errors.add attribute,
|
|
23
|
+
:invalid_string
|
|
24
|
+
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -5,7 +7,6 @@ module Sequent
|
|
|
5
7
|
TypeConversionError = Class.new(RuntimeError)
|
|
6
8
|
|
|
7
9
|
module Helpers
|
|
8
|
-
|
|
9
10
|
# Will parse all values to the correct types.
|
|
10
11
|
# The raw values are typically posted from the web and are therefor mostly strings.
|
|
11
12
|
# To parse a raw value your class must have a parse_from_string method that returns the parsed values.
|
|
@@ -14,8 +15,9 @@ module Sequent
|
|
|
14
15
|
def parse_attrs_to_correct_types
|
|
15
16
|
the_copy = dup
|
|
16
17
|
the_copy.class.types.each do |name, type|
|
|
17
|
-
raw_value = the_copy.send(
|
|
18
|
+
raw_value = the_copy.send(name.to_s)
|
|
18
19
|
next if raw_value.nil?
|
|
20
|
+
|
|
19
21
|
if raw_value.respond_to?(:parse_attrs_to_correct_types)
|
|
20
22
|
the_copy.send("#{name}=", raw_value.parse_attrs_to_correct_types)
|
|
21
23
|
else
|
|
@@ -24,7 +26,7 @@ module Sequent
|
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
the_copy
|
|
27
|
-
rescue => e
|
|
29
|
+
rescue StandardError => e
|
|
28
30
|
raise TypeConversionError, e.message
|
|
29
31
|
end
|
|
30
32
|
end
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'securerandom'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
4
6
|
module Core
|
|
5
7
|
module Helpers
|
|
6
8
|
module UuidHelper
|
|
7
|
-
|
|
8
9
|
def new_uuid
|
|
9
|
-
warn
|
|
10
|
+
warn <<~EOS
|
|
11
|
+
DEPRECATION WARNING: Sequent::Core::Helpers::UuidHelper.new_uuid is deprecated. Use Sequent.new_uuid instead
|
|
12
|
+
EOS
|
|
10
13
|
Sequent.new_uuid
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../ext/ext'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -6,7 +8,7 @@ module Sequent
|
|
|
6
8
|
class ValueValidators
|
|
7
9
|
INVALID_STRING_CHARS = [
|
|
8
10
|
"\u0000",
|
|
9
|
-
]
|
|
11
|
+
].freeze
|
|
10
12
|
|
|
11
13
|
VALIDATORS = {
|
|
12
14
|
::Symbol => ->(_) { true },
|
|
@@ -14,36 +16,48 @@ module Sequent
|
|
|
14
16
|
::Integer => ->(value) { valid_integer?(value) },
|
|
15
17
|
::Boolean => ->(value) { valid_bool?(value) },
|
|
16
18
|
::Date => ->(value) { valid_date?(value) },
|
|
17
|
-
::DateTime => ->(value) { valid_date_time?(value) }
|
|
18
|
-
}
|
|
19
|
+
::DateTime => ->(value) { valid_date_time?(value) },
|
|
20
|
+
}.freeze
|
|
19
21
|
|
|
20
22
|
def self.valid_integer?(value)
|
|
21
23
|
value.blank? || Integer(value)
|
|
22
|
-
rescue
|
|
24
|
+
rescue StandardError
|
|
23
25
|
false
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def self.valid_bool?(value)
|
|
27
29
|
return true if value.blank?
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass) || value == 'true' || value == 'false'
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def self.valid_date?(value)
|
|
32
35
|
return true if value.blank?
|
|
33
36
|
return true if value.is_a?(Date)
|
|
34
37
|
return false unless value =~ /\d{4}-\d{2}-\d{2}/
|
|
35
|
-
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
!!Date.iso8601(value)
|
|
41
|
+
rescue StandardError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def self.valid_date_time?(value)
|
|
39
47
|
return true if value.blank?
|
|
40
|
-
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
value.is_a?(DateTime) || !!DateTime.iso8601(value.dup)
|
|
51
|
+
rescue StandardError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
41
54
|
end
|
|
42
55
|
|
|
43
56
|
def self.valid_string?(value)
|
|
44
57
|
return true if value.nil?
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
|
|
59
|
+
value.to_s && INVALID_STRING_CHARS.none? { |invalid_char| value.to_s.include?(invalid_char) }
|
|
60
|
+
rescue StandardError
|
|
47
61
|
false
|
|
48
62
|
end
|
|
49
63
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
require_relative './persistor'
|
|
3
5
|
|
|
4
6
|
module Sequent
|
|
5
7
|
module Core
|
|
6
8
|
module Persistors
|
|
7
|
-
|
|
8
9
|
#
|
|
9
10
|
# The ActiveRecordPersistor uses ActiveRecord to update the projection
|
|
10
11
|
#
|
|
@@ -17,14 +18,21 @@ module Sequent
|
|
|
17
18
|
class ActiveRecordPersistor
|
|
18
19
|
include Persistor
|
|
19
20
|
|
|
20
|
-
def update_record(record_class, event, where_clause = {aggregate_id: event.aggregate_id}, options = {}
|
|
21
|
+
def update_record(record_class, event, where_clause = {aggregate_id: event.aggregate_id}, options = {})
|
|
21
22
|
record = record_class.unscoped.where(where_clause).first
|
|
22
|
-
|
|
23
|
+
unless record
|
|
24
|
+
fail(<<~EOS)
|
|
25
|
+
Record of class #{record_class} with where clause #{where_clause} not found while handling event #{event}
|
|
26
|
+
EOS
|
|
27
|
+
end
|
|
28
|
+
|
|
23
29
|
record.updated_at = event.created_at if record.respond_to?(:updated_at)
|
|
24
30
|
yield record if block_given?
|
|
25
|
-
update_sequence_number = options.key?(:update_sequence_number)
|
|
26
|
-
options[:update_sequence_number]
|
|
31
|
+
update_sequence_number = if options.key?(:update_sequence_number)
|
|
32
|
+
options[:update_sequence_number]
|
|
33
|
+
else
|
|
27
34
|
record.respond_to?(:sequence_number=)
|
|
35
|
+
end
|
|
28
36
|
record.sequence_number = event.sequence_number if update_sequence_number
|
|
29
37
|
record.save!
|
|
30
38
|
end
|
|
@@ -42,11 +50,13 @@ module Sequent
|
|
|
42
50
|
query = array_of_value_hashes.map do |values|
|
|
43
51
|
insert_manager = new_insert_manager
|
|
44
52
|
insert_manager.into(table)
|
|
45
|
-
insert_manager.insert(
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
insert_manager.insert(
|
|
54
|
+
values.map do |key, value|
|
|
55
|
+
convert_to_values(key, table, value)
|
|
56
|
+
end,
|
|
57
|
+
)
|
|
48
58
|
insert_manager.to_sql
|
|
49
|
-
end.join(
|
|
59
|
+
end.join(';')
|
|
50
60
|
|
|
51
61
|
execute_sql(query)
|
|
52
62
|
end
|