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
data/lib/sequent/core/event.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model'
|
|
2
4
|
require_relative 'helpers/string_support'
|
|
3
5
|
require_relative 'helpers/equal_support'
|
|
@@ -7,16 +9,17 @@ require_relative 'helpers/copyable'
|
|
|
7
9
|
module Sequent
|
|
8
10
|
module Core
|
|
9
11
|
class Event
|
|
10
|
-
include Sequent::Core::Helpers::
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
include Sequent::Core::Helpers::Copyable
|
|
13
|
+
include Sequent::Core::Helpers::AttributeSupport
|
|
14
|
+
include Sequent::Core::Helpers::EqualSupport
|
|
15
|
+
include Sequent::Core::Helpers::StringSupport
|
|
14
16
|
attrs aggregate_id: String, sequence_number: Integer, created_at: DateTime
|
|
15
17
|
|
|
16
18
|
def initialize(args = {})
|
|
17
19
|
update_all_attributes args
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
fail 'Missing aggregate_id' unless @aggregate_id
|
|
21
|
+
fail 'Missing sequence_number' unless @sequence_number
|
|
22
|
+
|
|
20
23
|
@created_at ||= DateTime.now
|
|
21
24
|
end
|
|
22
25
|
|
|
@@ -24,22 +27,24 @@ module Sequent
|
|
|
24
27
|
result = {}
|
|
25
28
|
instance_variables
|
|
26
29
|
.reject { |k| payload_variables.include?(k) }
|
|
27
|
-
.select { |k| self.class.types.keys.include?(to_attribute_name(k))}
|
|
30
|
+
.select { |k| self.class.types.keys.include?(to_attribute_name(k)) }
|
|
28
31
|
.each do |k|
|
|
29
|
-
result[k.to_s[1
|
|
32
|
+
result[k.to_s[1..-1].to_sym] = instance_variable_get(k)
|
|
30
33
|
end
|
|
31
34
|
result
|
|
32
35
|
end
|
|
36
|
+
|
|
33
37
|
protected
|
|
38
|
+
|
|
34
39
|
def payload_variables
|
|
35
|
-
%i
|
|
40
|
+
%i[@aggregate_id @sequence_number @created_at]
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
private
|
|
44
|
+
|
|
39
45
|
def to_attribute_name(instance_variable_name)
|
|
40
|
-
instance_variable_name[1
|
|
46
|
+
instance_variable_name[1..-1].to_sym
|
|
41
47
|
end
|
|
42
|
-
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
class SnapshotEvent < Event
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
#
|
|
4
|
-
# EventPublisher ensures that, for every thread, events will be published
|
|
6
|
+
# EventPublisher ensures that, for every thread, events will be published
|
|
7
|
+
# in the order in which they are queued for publishing.
|
|
5
8
|
#
|
|
6
|
-
# This potentially introduces a wrinkle into your plans:
|
|
9
|
+
# This potentially introduces a wrinkle into your plans:
|
|
10
|
+
# You therefore should not split a "unit of work" across multiple threads.
|
|
7
11
|
#
|
|
8
|
-
# If you want other behaviour, you are free to implement your own version of EventPublisher
|
|
12
|
+
# If you want other behaviour, you are free to implement your own version of EventPublisher
|
|
13
|
+
# and configure Sequent to use it.
|
|
9
14
|
#
|
|
10
15
|
class EventPublisher
|
|
11
16
|
class PublishEventError < RuntimeError
|
|
12
17
|
attr_reader :event_handler_class, :event
|
|
13
18
|
|
|
14
19
|
def initialize(event_handler_class, event)
|
|
20
|
+
super()
|
|
15
21
|
@event_handler_class = event_handler_class
|
|
16
22
|
@event = event
|
|
17
23
|
end
|
|
@@ -23,6 +29,7 @@ module Sequent
|
|
|
23
29
|
|
|
24
30
|
def publish_events(events)
|
|
25
31
|
return if configuration.disable_event_handlers
|
|
32
|
+
|
|
26
33
|
events.each { |event| events_queue.push(event) }
|
|
27
34
|
process_events
|
|
28
35
|
end
|
|
@@ -35,13 +42,9 @@ module Sequent
|
|
|
35
42
|
|
|
36
43
|
def process_events
|
|
37
44
|
Sequent::Util.skip_if_already_processing(:events_queue_lock) do
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
ensure
|
|
43
|
-
events_queue.clear
|
|
44
|
-
end
|
|
45
|
+
process_event(events_queue.pop) until events_queue.empty?
|
|
46
|
+
ensure
|
|
47
|
+
events_queue.clear
|
|
45
48
|
end
|
|
46
49
|
end
|
|
47
50
|
|
|
@@ -51,11 +54,9 @@ module Sequent
|
|
|
51
54
|
Sequent.logger.debug("[EventPublisher] Publishing event #{event.class}")
|
|
52
55
|
|
|
53
56
|
configuration.event_handlers.each do |handler|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise PublishEventError.new(handler.class, event)
|
|
58
|
-
end
|
|
57
|
+
handler.handle_message event
|
|
58
|
+
rescue StandardError
|
|
59
|
+
raise PublishEventError.new(handler.class, event)
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
require_relative 'sequent_oj'
|
|
3
|
-
require_relative '../application_record
|
|
5
|
+
require_relative '../application_record'
|
|
4
6
|
|
|
5
7
|
module Sequent
|
|
6
8
|
module Core
|
|
7
|
-
|
|
8
9
|
# == Event Record Hooks
|
|
9
10
|
#
|
|
10
11
|
# These hooks are called during the life cycle of
|
|
@@ -27,7 +28,6 @@ module Sequent
|
|
|
27
28
|
# end
|
|
28
29
|
# end
|
|
29
30
|
class EventRecordHooks
|
|
30
|
-
|
|
31
31
|
# Called after assigning Sequent's event attributes to the +event_record+.
|
|
32
32
|
#
|
|
33
33
|
# *Params*
|
|
@@ -42,13 +42,12 @@ module Sequent
|
|
|
42
42
|
def self.after_serialization(event_record, event)
|
|
43
43
|
# noop
|
|
44
44
|
end
|
|
45
|
-
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
module SerializesEvent
|
|
49
48
|
def event
|
|
50
|
-
payload = Sequent::Core::Oj.strict_load(
|
|
51
|
-
Class.const_get(
|
|
49
|
+
payload = Sequent::Core::Oj.strict_load(event_json)
|
|
50
|
+
Class.const_get(event_type).deserialize_from_json(payload)
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
def event=(event)
|
|
@@ -76,7 +75,7 @@ module Sequent
|
|
|
76
75
|
class EventRecord < Sequent::ApplicationRecord
|
|
77
76
|
include SerializesEvent
|
|
78
77
|
|
|
79
|
-
self.table_name =
|
|
78
|
+
self.table_name = 'event_records'
|
|
80
79
|
|
|
81
80
|
belongs_to :stream_record
|
|
82
81
|
belongs_to :command_record
|
|
@@ -98,6 +97,7 @@ module Sequent
|
|
|
98
97
|
|
|
99
98
|
def find_origin(record)
|
|
100
99
|
return find_origin(record.parent) if record.parent.present?
|
|
100
|
+
|
|
101
101
|
record
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'forwardable'
|
|
2
4
|
require_relative 'event_record'
|
|
3
5
|
require_relative 'sequent_oj'
|
|
4
6
|
|
|
5
7
|
module Sequent
|
|
6
8
|
module Core
|
|
7
|
-
|
|
8
9
|
class EventStore
|
|
9
10
|
include ActiveRecord::ConnectionAdapters::Quoting
|
|
10
11
|
extend Forwardable
|
|
@@ -16,13 +17,13 @@ module Sequent
|
|
|
16
17
|
attr_reader :event_hash
|
|
17
18
|
|
|
18
19
|
def initialize(event_hash)
|
|
20
|
+
super()
|
|
19
21
|
@event_hash = event_hash
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def message
|
|
23
25
|
"Event hash: #{event_hash.inspect}\nCause: #{cause.inspect}"
|
|
24
26
|
end
|
|
25
|
-
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def initialize
|
|
@@ -41,7 +42,7 @@ module Sequent
|
|
|
41
42
|
# `StreamRecord` to arrays ordered uncommitted `Event`s.
|
|
42
43
|
#
|
|
43
44
|
def commit_events(command, streams_with_events)
|
|
44
|
-
fail ArgumentError,
|
|
45
|
+
fail ArgumentError, 'command is required' if command.nil?
|
|
45
46
|
|
|
46
47
|
Sequent.logger.debug("[EventStore] Committing events for command #{command.class}")
|
|
47
48
|
|
|
@@ -61,27 +62,36 @@ module Sequent
|
|
|
61
62
|
|
|
62
63
|
streams = Sequent.configuration.stream_record_class.where(aggregate_id: aggregate_ids)
|
|
63
64
|
|
|
64
|
-
query = aggregate_ids.uniq.map { |aggregate_id| aggregate_query(aggregate_id) }.join(
|
|
65
|
-
events = Sequent.configuration.event_record_class.connection.select_all(query).map
|
|
65
|
+
query = aggregate_ids.uniq.map { |aggregate_id| aggregate_query(aggregate_id) }.join(' UNION ALL ')
|
|
66
|
+
events = Sequent.configuration.event_record_class.connection.select_all(query).map do |event_hash|
|
|
66
67
|
deserialize_event(event_hash)
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
events
|
|
70
|
-
.group_by
|
|
71
|
-
.map
|
|
71
|
+
.group_by(&:aggregate_id)
|
|
72
|
+
.map do |aggregate_id, es|
|
|
73
|
+
[
|
|
74
|
+
streams.find do |stream_record|
|
|
75
|
+
stream_record.aggregate_id == aggregate_id
|
|
76
|
+
end.event_stream,
|
|
77
|
+
es,
|
|
78
|
+
]
|
|
79
|
+
end
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def aggregate_query(aggregate_id)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
83
|
+
<<~SQL.chomp
|
|
84
|
+
(
|
|
85
|
+
SELECT event_type, event_json
|
|
86
|
+
FROM #{quote_table_name Sequent.configuration.event_record_class.table_name} AS o
|
|
87
|
+
WHERE aggregate_id = #{quote(aggregate_id)}
|
|
88
|
+
AND sequence_number >= COALESCE((SELECT MAX(sequence_number)
|
|
89
|
+
FROM #{quote_table_name Sequent.configuration.event_record_class.table_name} AS i
|
|
90
|
+
WHERE event_type = #{quote Sequent.configuration.snapshot_event_class.name}
|
|
91
|
+
AND i.aggregate_id = #{quote(aggregate_id)}), 0)
|
|
92
|
+
ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuration.snapshot_event_class.name} THEN 0 ELSE 1 END) ASC
|
|
93
|
+
)
|
|
94
|
+
SQL
|
|
85
95
|
end
|
|
86
96
|
|
|
87
97
|
def stream_exists?(aggregate_id)
|
|
@@ -97,7 +107,7 @@ ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuratio
|
|
|
97
107
|
# @param block that returns the events.
|
|
98
108
|
# <b>DEPRECATED:</b> use <tt>replay_events_from_cursor</tt> instead.
|
|
99
109
|
def replay_events
|
|
100
|
-
warn
|
|
110
|
+
warn '[DEPRECATION] `replay_events` is deprecated in favor of `replay_events_from_cursor`'
|
|
101
111
|
events = yield.map { |event_hash| deserialize_event(event_hash) }
|
|
102
112
|
publish_events(events)
|
|
103
113
|
end
|
|
@@ -109,8 +119,7 @@ ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuratio
|
|
|
109
119
|
#
|
|
110
120
|
# @param get_events lambda that returns the events cursor
|
|
111
121
|
# @param on_progress lambda that gets called on substantial progress
|
|
112
|
-
def replay_events_from_cursor(block_size: 2000,
|
|
113
|
-
get_events:,
|
|
122
|
+
def replay_events_from_cursor(get_events:, block_size: 2000,
|
|
114
123
|
on_progress: PRINT_PROGRESS)
|
|
115
124
|
progress = 0
|
|
116
125
|
cursor = get_events.call
|
|
@@ -128,7 +137,7 @@ ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuratio
|
|
|
128
137
|
on_progress[progress, true, ids_replayed]
|
|
129
138
|
end
|
|
130
139
|
|
|
131
|
-
PRINT_PROGRESS =
|
|
140
|
+
PRINT_PROGRESS = ->(progress, done, _) do
|
|
132
141
|
if done
|
|
133
142
|
Sequent.logger.debug "Done replaying #{progress} events"
|
|
134
143
|
else
|
|
@@ -142,38 +151,34 @@ ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuratio
|
|
|
142
151
|
def aggregates_that_need_snapshots(last_aggregate_id, limit = 10)
|
|
143
152
|
stream_table = quote_table_name Sequent.configuration.stream_record_class.table_name
|
|
144
153
|
event_table = quote_table_name Sequent.configuration.event_record_class.table_name
|
|
145
|
-
query =
|
|
146
|
-
SELECT aggregate_id
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
query = <<~SQL.chomp
|
|
155
|
+
SELECT aggregate_id
|
|
156
|
+
FROM #{stream_table} stream
|
|
157
|
+
WHERE aggregate_id::varchar > COALESCE(#{quote last_aggregate_id}, '')
|
|
158
|
+
AND snapshot_threshold IS NOT NULL
|
|
159
|
+
AND snapshot_threshold <= (
|
|
160
|
+
(SELECT MAX(events.sequence_number) FROM #{event_table} events WHERE events.event_type <> #{quote Sequent.configuration.snapshot_event_class.name} AND stream.aggregate_id = events.aggregate_id) -
|
|
161
|
+
COALESCE((SELECT MAX(snapshots.sequence_number) FROM #{event_table} snapshots WHERE snapshots.event_type = #{quote Sequent.configuration.snapshot_event_class.name} AND stream.aggregate_id = snapshots.aggregate_id), 0))
|
|
162
|
+
ORDER BY aggregate_id
|
|
163
|
+
LIMIT #{quote limit}
|
|
164
|
+
FOR UPDATE
|
|
165
|
+
SQL
|
|
157
166
|
Sequent.configuration.event_record_class.connection.select_all(query).map { |x| x['aggregate_id'] }
|
|
158
167
|
end
|
|
159
168
|
|
|
160
169
|
def find_event_stream(aggregate_id)
|
|
161
170
|
record = Sequent.configuration.stream_record_class.where(aggregate_id: aggregate_id).first
|
|
162
|
-
|
|
163
|
-
record.event_stream
|
|
164
|
-
else
|
|
165
|
-
nil
|
|
166
|
-
end
|
|
171
|
+
record&.event_stream
|
|
167
172
|
end
|
|
168
173
|
|
|
169
174
|
private
|
|
170
175
|
|
|
171
176
|
def column_names
|
|
172
177
|
@column_names ||= Sequent
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
.configuration
|
|
179
|
+
.event_record_class
|
|
180
|
+
.column_names
|
|
181
|
+
.reject { |c| c == primary_key_event_records }
|
|
177
182
|
end
|
|
178
183
|
|
|
179
184
|
def primary_key_event_records
|
|
@@ -181,11 +186,11 @@ SELECT aggregate_id
|
|
|
181
186
|
end
|
|
182
187
|
|
|
183
188
|
def deserialize_event(event_hash)
|
|
184
|
-
event_type = event_hash.fetch(
|
|
185
|
-
event_json = Sequent::Core::Oj.strict_load(event_hash.fetch(
|
|
189
|
+
event_type = event_hash.fetch('event_type')
|
|
190
|
+
event_json = Sequent::Core::Oj.strict_load(event_hash.fetch('event_json'))
|
|
186
191
|
resolve_event_type(event_type).deserialize_from_json(event_json)
|
|
187
|
-
rescue
|
|
188
|
-
raise DeserializeEventError
|
|
192
|
+
rescue StandardError
|
|
193
|
+
raise DeserializeEventError, event_hash
|
|
189
194
|
end
|
|
190
195
|
|
|
191
196
|
def resolve_event_type(event_type)
|
|
@@ -215,13 +220,15 @@ SELECT aggregate_id
|
|
|
215
220
|
end
|
|
216
221
|
connection = Sequent.configuration.event_record_class.connection
|
|
217
222
|
values = event_records
|
|
218
|
-
|
|
219
|
-
|
|
223
|
+
.map { |r| "(#{column_names.map { |c| connection.quote(r[c.to_sym]) }.join(',')})" }
|
|
224
|
+
.join(',')
|
|
220
225
|
columns = column_names.map { |c| connection.quote_column_name(c) }.join(',')
|
|
221
|
-
sql =
|
|
226
|
+
sql = <<~SQL.chomp
|
|
227
|
+
insert into #{connection.quote_table_name(Sequent.configuration.event_record_class.table_name)} (#{columns}) values #{values}
|
|
228
|
+
SQL
|
|
222
229
|
Sequent.configuration.event_record_class.connection.insert(sql, nil, primary_key_event_records)
|
|
223
230
|
rescue ActiveRecord::RecordNotUnique
|
|
224
|
-
|
|
231
|
+
raise OptimisticLockingError
|
|
225
232
|
end
|
|
226
233
|
end
|
|
227
234
|
end
|
data/lib/sequent/core/ext/ext.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Symbol
|
|
2
4
|
def self.deserialize_from_json(value)
|
|
3
5
|
value.blank? ? nil : value.try(:to_sym)
|
|
@@ -25,19 +27,25 @@ end
|
|
|
25
27
|
class BigDecimal
|
|
26
28
|
def self.deserialize_from_json(value)
|
|
27
29
|
return nil if value.nil?
|
|
30
|
+
|
|
28
31
|
BigDecimal(value)
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
module Boolean
|
|
33
36
|
def self.deserialize_from_json(value)
|
|
34
|
-
value.nil?
|
|
37
|
+
if value.nil?
|
|
38
|
+
nil
|
|
39
|
+
else
|
|
40
|
+
(value.present? ? value : false)
|
|
41
|
+
end
|
|
35
42
|
end
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
class Date
|
|
39
46
|
def self.from_params(value)
|
|
40
47
|
return value if value.is_a?(Date)
|
|
48
|
+
|
|
41
49
|
value.blank? ? nil : Date.iso8601(value.dup)
|
|
42
50
|
rescue ArgumentError
|
|
43
51
|
value
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -5,7 +7,8 @@ module Sequent
|
|
|
5
7
|
attr_accessor :item_type
|
|
6
8
|
|
|
7
9
|
def initialize(item_type)
|
|
8
|
-
|
|
10
|
+
fail 'needs a item_type' unless item_type
|
|
11
|
+
|
|
9
12
|
@item_type = item_type
|
|
10
13
|
end
|
|
11
14
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_model/validator'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -21,24 +23,23 @@ module Sequent
|
|
|
21
23
|
# validates_with Sequent::Core::AssociationValidator, associations: [:trainee]
|
|
22
24
|
#
|
|
23
25
|
class AssociationValidator < ActiveModel::Validator
|
|
24
|
-
|
|
25
26
|
def initialize(options = {})
|
|
26
27
|
super
|
|
27
|
-
|
|
28
|
+
fail "Must provide ':associations' to validate" unless options[:associations].present?
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def validate(record)
|
|
31
32
|
associations = options[:associations]
|
|
32
33
|
associations = [associations] unless associations.instance_of?(Array)
|
|
33
34
|
associations.each do |association|
|
|
34
|
-
value = record.instance_variable_get("@#{association
|
|
35
|
+
value = record.instance_variable_get("@#{association}")
|
|
35
36
|
if value && incorrect_type?(value, record, association)
|
|
36
37
|
record.errors.add(association, "is not of type #{describe_type(record.class.types[association])}")
|
|
37
|
-
elsif value
|
|
38
|
+
elsif value&.is_a?(Array)
|
|
38
39
|
item_type = record.class.types.fetch(association).item_type
|
|
39
|
-
record.errors.add(association,
|
|
40
|
-
|
|
41
|
-
record.errors.add(association,
|
|
40
|
+
record.errors.add(association, 'is invalid') unless validate_all(value, item_type).all?
|
|
41
|
+
elsif value&.invalid?
|
|
42
|
+
record.errors.add(association, 'is invalid')
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
|
@@ -47,6 +48,7 @@ module Sequent
|
|
|
47
48
|
|
|
48
49
|
def incorrect_type?(value, record, association)
|
|
49
50
|
return unless record.class.respond_to?(:types)
|
|
51
|
+
|
|
50
52
|
type = record.class.types[association]
|
|
51
53
|
if type.respond_to?(:candidate?)
|
|
52
54
|
!type.candidate?(value)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support'
|
|
2
4
|
require_relative '../ext/ext'
|
|
3
5
|
require_relative 'array_with_type'
|
|
@@ -38,18 +40,15 @@ module Sequent
|
|
|
38
40
|
|
|
39
41
|
# module containing class methods to be added
|
|
40
42
|
module ClassMethods
|
|
41
|
-
|
|
42
43
|
def types
|
|
43
44
|
@types ||= {}
|
|
44
|
-
if @merged_types
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@merged_types.merge!(mod.types)
|
|
50
|
-
end
|
|
51
|
-
@merged_types
|
|
45
|
+
return @merged_types if @merged_types
|
|
46
|
+
|
|
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)
|
|
52
50
|
end
|
|
51
|
+
@merged_types
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
def attrs(args)
|
|
@@ -58,14 +57,15 @@ module Sequent
|
|
|
58
57
|
associations = []
|
|
59
58
|
args.each do |attribute, type|
|
|
60
59
|
attr_accessor attribute
|
|
60
|
+
|
|
61
61
|
if included_modules.include?(Sequent::Core::Helpers::TypeConversionSupport)
|
|
62
62
|
Sequent::Core::Helpers::DefaultValidators.for(type).add_validations_for(self, attribute)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
if type.
|
|
65
|
+
if type.instance_of?(Sequent::Core::Helpers::ArrayWithType)
|
|
66
66
|
associations << attribute
|
|
67
67
|
elsif included_modules.include?(ActiveModel::Validations) &&
|
|
68
|
-
|
|
68
|
+
type.included_modules.include?(Sequent::Core::Helpers::AttributeSupport)
|
|
69
69
|
associations << attribute
|
|
70
70
|
end
|
|
71
71
|
end
|
|
@@ -77,9 +77,9 @@ module Sequent
|
|
|
77
77
|
def update_all_attributes(attrs)
|
|
78
78
|
super if defined?(super)
|
|
79
79
|
ensure_known_attributes(attrs)
|
|
80
|
-
#{@types.map
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
#{@types.map do |attribute, _|
|
|
81
|
+
"@#{attribute} = attrs[:#{attribute}]"
|
|
82
|
+
end.join("\n ")}
|
|
83
83
|
self
|
|
84
84
|
end
|
|
85
85
|
EOS
|
|
@@ -87,9 +87,9 @@ EOS
|
|
|
87
87
|
class_eval <<EOS
|
|
88
88
|
def update_all_attributes_from_json(attrs)
|
|
89
89
|
super if defined?(super)
|
|
90
|
-
#{@types.map
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
#{@types.map do |attribute, type|
|
|
91
|
+
"@#{attribute} = #{type}.deserialize_from_json(attrs['#{attribute}'])"
|
|
92
|
+
end.join("\n ")}
|
|
93
93
|
end
|
|
94
94
|
EOS
|
|
95
95
|
end
|
|
@@ -106,17 +106,33 @@ EOS
|
|
|
106
106
|
|
|
107
107
|
def deserialize_from_json(args)
|
|
108
108
|
unless args.nil?
|
|
109
|
-
obj = allocate
|
|
109
|
+
obj = allocate
|
|
110
|
+
|
|
111
|
+
upcast!(args)
|
|
112
|
+
|
|
110
113
|
obj.update_all_attributes_from_json(args)
|
|
111
114
|
obj
|
|
112
115
|
end
|
|
113
116
|
end
|
|
114
117
|
|
|
115
|
-
|
|
116
118
|
def numeric?(object)
|
|
117
|
-
true if Float(object)
|
|
119
|
+
true if Float(object)
|
|
120
|
+
rescue StandardError
|
|
121
|
+
false
|
|
118
122
|
end
|
|
119
123
|
|
|
124
|
+
def upcast(&block)
|
|
125
|
+
@upcasters ||= []
|
|
126
|
+
@upcasters.push(block)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def upcast!(hash)
|
|
130
|
+
return if @upcasters.nil?
|
|
131
|
+
|
|
132
|
+
@upcasters.each do |upcaster|
|
|
133
|
+
upcaster.call(hash)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
120
136
|
end
|
|
121
137
|
|
|
122
138
|
# extend host class with class methods when we're included
|
|
@@ -124,11 +140,10 @@ EOS
|
|
|
124
140
|
host_class.extend(ClassMethods)
|
|
125
141
|
end
|
|
126
142
|
|
|
127
|
-
|
|
128
143
|
def attributes
|
|
129
144
|
hash = HashWithIndifferentAccess.new
|
|
130
145
|
self.class.types.each do |name, _|
|
|
131
|
-
value =
|
|
146
|
+
value = instance_variable_get("@#{name}")
|
|
132
147
|
hash[name] = if value.respond_to?(:attributes)
|
|
133
148
|
value.attributes
|
|
134
149
|
else
|
|
@@ -141,7 +156,7 @@ EOS
|
|
|
141
156
|
def as_json(opts = {})
|
|
142
157
|
hash = HashWithIndifferentAccess.new
|
|
143
158
|
self.class.types.each do |name, _|
|
|
144
|
-
value =
|
|
159
|
+
value = instance_variable_get("@#{name}")
|
|
145
160
|
hash[name] = if value.respond_to?(:as_json)
|
|
146
161
|
value.as_json(opts)
|
|
147
162
|
else
|
|
@@ -158,15 +173,15 @@ EOS
|
|
|
158
173
|
def validation_errors(prefix = nil)
|
|
159
174
|
result = errors.to_hash
|
|
160
175
|
self.class.types.each do |field|
|
|
161
|
-
value =
|
|
176
|
+
value = instance_variable_get("@#{field[0]}")
|
|
162
177
|
if value.respond_to? :validation_errors
|
|
163
|
-
value.validation_errors.each { |k, v| result["#{field[0]
|
|
164
|
-
elsif field[1].
|
|
178
|
+
value.validation_errors.each { |k, v| result["#{field[0]}_#{k}".to_sym] = v }
|
|
179
|
+
elsif field[1].instance_of?(ArrayWithType) && value.present?
|
|
165
180
|
value
|
|
166
181
|
.select { |val| val.respond_to?(:validation_errors) }
|
|
167
182
|
.each_with_index do |val, index|
|
|
168
183
|
val.validation_errors.each do |k, v|
|
|
169
|
-
result["#{field[0]
|
|
184
|
+
result["#{field[0]}_#{index}_#{k}".to_sym] = v
|
|
170
185
|
end
|
|
171
186
|
end
|
|
172
187
|
end
|
|
@@ -178,7 +193,9 @@ EOS
|
|
|
178
193
|
return unless Sequent.configuration.strict_check_attributes_on_apply_events
|
|
179
194
|
|
|
180
195
|
unknowns = attrs.keys.map(&:to_s) - self.class.types.keys.map(&:to_s)
|
|
181
|
-
|
|
196
|
+
if unknowns.any?
|
|
197
|
+
fail UnknownAttributeError, "#{self.class.name} does not specify attrs: #{unknowns.join(', ')}"
|
|
198
|
+
end
|
|
182
199
|
end
|
|
183
200
|
end
|
|
184
201
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
module Helpers
|
|
@@ -24,8 +26,7 @@ module Sequent
|
|
|
24
26
|
#
|
|
25
27
|
module AutosetAttributes
|
|
26
28
|
module ClassMethods
|
|
27
|
-
|
|
28
|
-
@@autoset_ignore_attributes = %w{aggregate_id sequence_number created_at}
|
|
29
|
+
@@autoset_ignore_attributes = %w[aggregate_id sequence_number created_at]
|
|
29
30
|
|
|
30
31
|
def set_autoset_ignore_attributes(attribute_names)
|
|
31
32
|
@@autoset_ignore_attributes = attribute_names
|
|
@@ -39,7 +40,7 @@ module Sequent
|
|
|
39
40
|
event_classes.each do |event_class|
|
|
40
41
|
on event_class do |event|
|
|
41
42
|
self.class.event_attribute_keys(event_class).each do |attribute_name|
|
|
42
|
-
instance_variable_set(:"@#{attribute_name
|
|
43
|
+
instance_variable_set(:"@#{attribute_name}", event.send(attribute_name.to_sym))
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
end
|
|
@@ -53,4 +54,3 @@ module Sequent
|
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
|
-
|