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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequent +31 -25
  3. data/lib/notices.rb +2 -0
  4. data/lib/sequent/application_record.rb +2 -0
  5. data/lib/sequent/configuration.rb +24 -31
  6. data/lib/sequent/core/aggregate_repository.rb +17 -13
  7. data/lib/sequent/core/aggregate_root.rb +16 -7
  8. data/lib/sequent/core/aggregate_roots.rb +24 -0
  9. data/lib/sequent/core/aggregate_snapshotter.rb +8 -5
  10. data/lib/sequent/core/base_command_handler.rb +4 -2
  11. data/lib/sequent/core/command.rb +17 -9
  12. data/lib/sequent/core/command_record.rb +8 -3
  13. data/lib/sequent/core/command_service.rb +18 -18
  14. data/lib/sequent/core/core.rb +2 -0
  15. data/lib/sequent/core/current_event.rb +2 -0
  16. data/lib/sequent/core/event.rb +16 -11
  17. data/lib/sequent/core/event_publisher.rb +16 -15
  18. data/lib/sequent/core/event_record.rb +7 -7
  19. data/lib/sequent/core/event_store.rb +57 -50
  20. data/lib/sequent/core/ext/ext.rb +9 -1
  21. data/lib/sequent/core/helpers/array_with_type.rb +4 -1
  22. data/lib/sequent/core/helpers/association_validator.rb +9 -7
  23. data/lib/sequent/core/helpers/attribute_support.rb +45 -28
  24. data/lib/sequent/core/helpers/autoset_attributes.rb +4 -4
  25. data/lib/sequent/core/helpers/boolean_validator.rb +6 -1
  26. data/lib/sequent/core/helpers/copyable.rb +2 -2
  27. data/lib/sequent/core/helpers/date_time_validator.rb +4 -1
  28. data/lib/sequent/core/helpers/date_validator.rb +6 -1
  29. data/lib/sequent/core/helpers/default_validators.rb +12 -10
  30. data/lib/sequent/core/helpers/equal_support.rb +8 -6
  31. data/lib/sequent/core/helpers/helpers.rb +2 -0
  32. data/lib/sequent/core/helpers/mergable.rb +6 -5
  33. data/lib/sequent/core/helpers/message_handler.rb +3 -1
  34. data/lib/sequent/core/helpers/param_support.rb +19 -15
  35. data/lib/sequent/core/helpers/secret.rb +14 -12
  36. data/lib/sequent/core/helpers/string_support.rb +5 -4
  37. data/lib/sequent/core/helpers/string_to_value_parsers.rb +7 -2
  38. data/lib/sequent/core/helpers/string_validator.rb +6 -1
  39. data/lib/sequent/core/helpers/type_conversion_support.rb +5 -3
  40. data/lib/sequent/core/helpers/uuid_helper.rb +5 -2
  41. data/lib/sequent/core/helpers/value_validators.rb +23 -9
  42. data/lib/sequent/core/persistors/active_record_persistor.rb +19 -9
  43. data/lib/sequent/core/persistors/persistor.rb +16 -14
  44. data/lib/sequent/core/persistors/persistors.rb +2 -0
  45. data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +70 -47
  46. data/lib/sequent/core/projector.rb +25 -22
  47. data/lib/sequent/core/random_uuid_generator.rb +2 -0
  48. data/lib/sequent/core/sequent_oj.rb +2 -0
  49. data/lib/sequent/core/stream_record.rb +9 -3
  50. data/lib/sequent/core/transactions/active_record_transaction_provider.rb +5 -9
  51. data/lib/sequent/core/transactions/no_transactions.rb +2 -1
  52. data/lib/sequent/core/transactions/transactions.rb +2 -0
  53. data/lib/sequent/core/value_object.rb +8 -10
  54. data/lib/sequent/core/workflow.rb +7 -5
  55. data/lib/sequent/generator/aggregate.rb +16 -10
  56. data/lib/sequent/generator/command.rb +26 -19
  57. data/lib/sequent/generator/event.rb +19 -17
  58. data/lib/sequent/generator/generator.rb +2 -0
  59. data/lib/sequent/generator/project.rb +2 -0
  60. data/lib/sequent/generator/template_project/Gemfile +1 -1
  61. data/lib/sequent/generator.rb +2 -0
  62. data/lib/sequent/migrations/executor.rb +22 -13
  63. data/lib/sequent/migrations/functions.rb +5 -6
  64. data/lib/sequent/migrations/migrate_events.rb +12 -9
  65. data/lib/sequent/migrations/migrations.rb +2 -1
  66. data/lib/sequent/migrations/planner.rb +33 -23
  67. data/lib/sequent/migrations/projectors.rb +4 -3
  68. data/lib/sequent/migrations/sql.rb +2 -0
  69. data/lib/sequent/migrations/view_schema.rb +84 -45
  70. data/lib/sequent/rake/migration_tasks.rb +58 -22
  71. data/lib/sequent/rake/tasks.rb +5 -2
  72. data/lib/sequent/sequent.rb +2 -0
  73. data/lib/sequent/support/database.rb +30 -15
  74. data/lib/sequent/support/view_projection.rb +6 -3
  75. data/lib/sequent/support/view_schema.rb +2 -0
  76. data/lib/sequent/support.rb +2 -0
  77. data/lib/sequent/test/command_handler_helpers.rb +35 -17
  78. data/lib/sequent/test/event_handler_helpers.rb +10 -4
  79. data/lib/sequent/test/event_stream_helpers.rb +7 -3
  80. data/lib/sequent/test/time_comparison.rb +12 -5
  81. data/lib/sequent/test.rb +2 -0
  82. data/lib/sequent/util/dry_run.rb +11 -8
  83. data/lib/sequent/util/printer.rb +6 -5
  84. data/lib/sequent/util/skip_if_already_processing.rb +3 -1
  85. data/lib/sequent/util/timer.rb +2 -0
  86. data/lib/sequent/util/util.rb +2 -0
  87. data/lib/sequent.rb +2 -0
  88. data/lib/version.rb +3 -1
  89. metadata +81 -66
@@ -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::StringSupport,
11
- Sequent::Core::Helpers::EqualSupport,
12
- Sequent::Core::Helpers::AttributeSupport,
13
- Sequent::Core::Helpers::Copyable
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
- raise "Missing aggregate_id" unless @aggregate_id
19
- raise "Missing sequence_number" unless @sequence_number
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 .. -1].to_sym] = instance_variable_get(k)
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{@aggregate_id @sequence_number @created_at}
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 .. -1].to_sym
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 in the order in which they are queued for publishing.
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: You therefore should not split a "unit of work" across multiple threads.
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 and configure Sequent to use it.
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
- begin
39
- while(!events_queue.empty?) do
40
- process_event(events_queue.pop)
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
- begin
55
- handler.handle_message event
56
- rescue
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.rb'
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(self.event_json)
51
- Class.const_get(self.event_type).deserialize_from_json(payload)
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 = "event_records"
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, "command is required" if command.nil?
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(" UNION ALL ")
65
- events = Sequent.configuration.event_record_class.connection.select_all(query).map! do |event_hash|
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 { |event| event.aggregate_id }
71
- .map { |aggregate_id, _events| [streams.find { |stream_record| stream_record.aggregate_id == aggregate_id }.event_stream, _events] }
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
- %Q{(
76
- SELECT event_type, event_json
77
- FROM #{quote_table_name Sequent.configuration.event_record_class.table_name} AS o
78
- WHERE aggregate_id = #{quote(aggregate_id)}
79
- AND sequence_number >= COALESCE((SELECT MAX(sequence_number)
80
- FROM #{quote_table_name Sequent.configuration.event_record_class.table_name} AS i
81
- WHERE event_type = #{quote Sequent.configuration.snapshot_event_class.name}
82
- AND i.aggregate_id = #{quote(aggregate_id)}), 0)
83
- ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote Sequent.configuration.snapshot_event_class.name} THEN 0 ELSE 1 END) ASC
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 "[DEPRECATION] `replay_events` is deprecated in favor of `replay_events_from_cursor`"
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 = lambda do |progress, done, _|
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 = %Q{
146
- SELECT aggregate_id
147
- FROM #{stream_table} stream
148
- WHERE aggregate_id::varchar > COALESCE(#{quote last_aggregate_id}, '')
149
- AND snapshot_threshold IS NOT NULL
150
- AND snapshot_threshold <= (
151
- (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) -
152
- 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))
153
- ORDER BY aggregate_id
154
- LIMIT #{quote limit}
155
- FOR UPDATE
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
- if record
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
- .configuration
174
- .event_record_class
175
- .column_names
176
- .reject { |c| c == primary_key_event_records }
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("event_type")
185
- event_json = Sequent::Core::Oj.strict_load(event_hash.fetch("event_json"))
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.new(event_hash)
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
- .map { |r| "(#{column_names.map { |c| connection.quote(r[c.to_sym]) }.join(',')})" }
219
- .join(',')
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 = %Q{insert into #{connection.quote_table_name(Sequent.configuration.event_record_class.table_name)} (#{columns}) values #{values}}
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
- fail OptimisticLockingError.new
231
+ raise OptimisticLockingError
225
232
  end
226
233
  end
227
234
  end
@@ -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? ? nil : (value.present? ? value : false)
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
- raise "needs a item_type" unless item_type
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
- raise "Must provide ':associations' to validate" unless options[:associations].present?
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.to_s}")
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 && value.is_a?(Array)
38
+ elsif value&.is_a?(Array)
38
39
  item_type = record.class.types.fetch(association).item_type
39
- record.errors.add(association, "is invalid") unless validate_all(value, item_type).all?
40
- else
41
- record.errors.add(association, "is invalid") if value && value.invalid?
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
- @merged_types
46
- else
47
- @merged_types = is_a?(Class) && superclass.respond_to?(:types) ? @types.merge(superclass.types) : @types
48
- included_modules.select { |m| m.include? Sequent::Core::Helpers::AttributeSupport }.each do |mod|
49
- @merged_types.merge!(mod.types)
50
- end
51
- @merged_types
45
+ 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.class == Sequent::Core::Helpers::ArrayWithType
65
+ if type.instance_of?(Sequent::Core::Helpers::ArrayWithType)
66
66
  associations << attribute
67
67
  elsif included_modules.include?(ActiveModel::Validations) &&
68
- type.included_modules.include?(Sequent::Core::Helpers::AttributeSupport)
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 { |attribute, _|
81
- "@#{attribute} = attrs[:#{attribute}]"
82
- }.join("\n ")}
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 { |attribute, type|
91
- "@#{attribute} = #{type}.deserialize_from_json(attrs['#{attribute}'])"
92
- }.join("\n ")}
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) rescue false
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 = self.instance_variable_get("@#{name}")
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 = self.instance_variable_get("@#{name}")
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 = self.instance_variable_get("@#{field[0]}")
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].to_s}_#{k.to_s}".to_sym] = v }
164
- elsif field[1].class == ArrayWithType and value.present?
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].to_s}_#{index}_#{k.to_s}".to_sym] = v
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
- raise UnknownAttributeError.new("#{self.class.name} does not specify attrs: #{unknowns.join(", ")}") if unknowns.any?
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.to_s}", event.send(attribute_name.to_sym))
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
-