sequent 7.1.1 → 8.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequent +6 -107
  3. data/db/sequent_8_migration.sql +120 -0
  4. data/db/sequent_pgsql.sql +416 -0
  5. data/db/sequent_schema.rb +11 -57
  6. data/db/sequent_schema_indexes.sql +37 -0
  7. data/db/sequent_schema_partitions.sql +34 -0
  8. data/db/sequent_schema_tables.sql +74 -0
  9. data/lib/sequent/cli/app.rb +132 -0
  10. data/lib/sequent/cli/sequent_8_migration.rb +180 -0
  11. data/lib/sequent/configuration.rb +11 -8
  12. data/lib/sequent/core/aggregate_repository.rb +2 -2
  13. data/lib/sequent/core/aggregate_root.rb +32 -9
  14. data/lib/sequent/core/aggregate_snapshotter.rb +8 -6
  15. data/lib/sequent/core/command_record.rb +27 -18
  16. data/lib/sequent/core/command_service.rb +2 -2
  17. data/lib/sequent/core/event_publisher.rb +1 -1
  18. data/lib/sequent/core/event_record.rb +37 -17
  19. data/lib/sequent/core/event_store.rb +101 -119
  20. data/lib/sequent/core/helpers/array_with_type.rb +1 -1
  21. data/lib/sequent/core/helpers/association_validator.rb +2 -2
  22. data/lib/sequent/core/helpers/attribute_support.rb +8 -8
  23. data/lib/sequent/core/helpers/equal_support.rb +3 -3
  24. data/lib/sequent/core/helpers/message_matchers/has_attrs.rb +2 -0
  25. data/lib/sequent/core/helpers/message_router.rb +2 -2
  26. data/lib/sequent/core/helpers/param_support.rb +1 -3
  27. data/lib/sequent/core/helpers/pgsql_helpers.rb +32 -0
  28. data/lib/sequent/core/helpers/string_support.rb +1 -1
  29. data/lib/sequent/core/helpers/string_to_value_parsers.rb +1 -1
  30. data/lib/sequent/core/persistors/active_record_persistor.rb +1 -1
  31. data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +3 -4
  32. data/lib/sequent/core/projector.rb +1 -1
  33. data/lib/sequent/core/snapshot_record.rb +44 -0
  34. data/lib/sequent/core/snapshot_store.rb +105 -0
  35. data/lib/sequent/core/stream_record.rb +10 -15
  36. data/lib/sequent/dry_run/read_only_replay_optimized_postgres_persistor.rb +1 -1
  37. data/lib/sequent/dry_run/view_schema.rb +2 -3
  38. data/lib/sequent/generator/project.rb +5 -7
  39. data/lib/sequent/generator/template_aggregate/template_aggregate/commands.rb +2 -0
  40. data/lib/sequent/generator/template_aggregate/template_aggregate/events.rb +2 -0
  41. data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate.rb +2 -0
  42. data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate_command_handler.rb +2 -0
  43. data/lib/sequent/generator/template_aggregate/template_aggregate.rb +2 -0
  44. data/lib/sequent/generator/template_project/Gemfile +7 -5
  45. data/lib/sequent/generator/template_project/Rakefile +4 -2
  46. data/lib/sequent/generator/template_project/app/projectors/post_projector.rb +2 -0
  47. data/lib/sequent/generator/template_project/app/records/post_record.rb +2 -0
  48. data/lib/sequent/generator/template_project/config/initializers/sequent.rb +3 -8
  49. data/lib/sequent/generator/template_project/db/migrations.rb +3 -3
  50. data/lib/sequent/generator/template_project/lib/post/commands.rb +2 -0
  51. data/lib/sequent/generator/template_project/lib/post/events.rb +2 -0
  52. data/lib/sequent/generator/template_project/lib/post/post.rb +2 -0
  53. data/lib/sequent/generator/template_project/lib/post/post_command_handler.rb +2 -0
  54. data/lib/sequent/generator/template_project/lib/post.rb +2 -0
  55. data/lib/sequent/generator/template_project/my_app.rb +2 -1
  56. data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +2 -0
  57. data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +9 -2
  58. data/lib/sequent/generator/template_project/spec/spec_helper.rb +4 -7
  59. data/lib/sequent/generator.rb +1 -1
  60. data/lib/sequent/internal/aggregate_type.rb +12 -0
  61. data/lib/sequent/internal/command_type.rb +12 -0
  62. data/lib/sequent/internal/event_type.rb +12 -0
  63. data/lib/sequent/internal/internal.rb +14 -0
  64. data/lib/sequent/internal/partitioned_aggregate.rb +26 -0
  65. data/lib/sequent/internal/partitioned_command.rb +16 -0
  66. data/lib/sequent/internal/partitioned_event.rb +29 -0
  67. data/lib/sequent/migrations/grouper.rb +90 -0
  68. data/lib/sequent/migrations/sequent_schema.rb +2 -1
  69. data/lib/sequent/migrations/view_schema.rb +76 -77
  70. data/lib/sequent/rake/migration_tasks.rb +49 -24
  71. data/lib/sequent/sequent.rb +1 -0
  72. data/lib/sequent/support/database.rb +20 -16
  73. data/lib/sequent/test/time_comparison.rb +1 -1
  74. data/lib/sequent/util/timer.rb +1 -1
  75. data/lib/version.rb +1 -1
  76. metadata +102 -21
  77. data/lib/sequent/generator/template_project/db/sequent_schema.rb +0 -52
  78. data/lib/sequent/generator/template_project/ruby-version +0 -1
@@ -63,13 +63,13 @@ module Sequent
63
63
  Sequent::Core::Helpers::DefaultValidators.for(type).add_validations_for(self, attribute)
64
64
  end
65
65
 
66
- if type.instance_of?(Sequent::Core::Helpers::ArrayWithType)
67
- associations << attribute
68
- elsif included_modules.include?(ActiveModel::Validations) &&
69
- type.included_modules.include?(Sequent::Core::Helpers::AttributeSupport)
70
- associations << attribute
71
- end
66
+ is_array = type.instance_of?(Sequent::Core::Helpers::ArrayWithType)
67
+ needs_validation = !is_array && included_modules.include?(ActiveModel::Validations) &&
68
+ type.included_modules.include?(Sequent::Core::Helpers::AttributeSupport)
69
+
70
+ associations << attribute if is_array || needs_validation
72
71
  end
72
+
73
73
  if included_modules.include?(ActiveModel::Validations) && associations.present?
74
74
  validates_with Sequent::Core::Helpers::AssociationValidator, associations: associations
75
75
  end
@@ -168,7 +168,7 @@ EOS
168
168
 
169
169
  def attributes
170
170
  hash = HashWithIndifferentAccess.new
171
- self.class.types.each do |name, _|
171
+ self.class.types.each_key do |name|
172
172
  value = instance_variable_get("@#{name}")
173
173
  hash[name] = if value.respond_to?(:attributes)
174
174
  value.attributes
@@ -181,7 +181,7 @@ EOS
181
181
 
182
182
  def as_json(opts = {})
183
183
  hash = HashWithIndifferentAccess.new
184
- self.class.types.each do |name, _|
184
+ self.class.types.each_key do |name|
185
185
  value = instance_variable_get("@#{name}")
186
186
  hash[name] = if value.respond_to?(:as_json)
187
187
  value.as_json(opts)
@@ -13,7 +13,7 @@ module Sequent
13
13
  return false if other.nil?
14
14
  return false if self.class != other.class
15
15
 
16
- self.class.types.each do |name, _|
16
+ self.class.types.each_key do |name|
17
17
  self_value = send(name)
18
18
  other_value = other.send(name)
19
19
  if self_value.class == DateTime && other_value.class == DateTime
@@ -28,8 +28,8 @@ module Sequent
28
28
 
29
29
  def hash
30
30
  hash = 17
31
- self.class.types.each do |name, _|
32
- hash = hash * 31 + send(name).hash
31
+ self.class.types.each_key do |name|
32
+ hash = (hash * 31) + send(name).hash
33
33
  end
34
34
  hash
35
35
  end
@@ -20,10 +20,12 @@ module Sequent
20
20
  end
21
21
 
22
22
  def to_s
23
+ # rubocop:disable Layout/LineEndStringConcatenationIndentation
23
24
  'has_attrs(' \
24
25
  "#{MessageMatchers::ArgumentSerializer.serialize_value(message_matcher)}, " \
25
26
  "#{AttrMatchers::ArgumentSerializer.serialize_value(expected_attrs)}" \
26
27
  ')'
28
+ # rubocop:enable Layout/LineEndStringConcatenationIndentation
27
29
  end
28
30
 
29
31
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './attr_matchers/attr_matchers'
4
- require_relative './message_matchers/message_matchers'
3
+ require_relative 'attr_matchers/attr_matchers'
4
+ require_relative 'message_matchers/message_matchers'
5
5
 
6
6
  module Sequent
7
7
  module Core
@@ -79,9 +79,7 @@ module Sequent
79
79
  def value_to_string(val)
80
80
  if val.is_a?(Sequent::Core::ValueObject)
81
81
  val.as_params
82
- elsif val.is_a? DateTime
83
- val.iso8601
84
- elsif val.is_a? Date
82
+ elsif val.is_a?(DateTime) || val.is_a?(Date)
85
83
  val.iso8601
86
84
  elsif val.is_a? Time
87
85
  val.iso8601(Sequent.configuration.time_precision)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequent
4
+ module Core
5
+ module Helpers
6
+ module PgsqlHelpers
7
+ def call_procedure(connection, procedure, params)
8
+ fail ArgumentError if procedure.blank?
9
+
10
+ statement = "CALL #{quote_ident(procedure)}(#{bind_placeholders(params)})"
11
+ connection.exec_update(statement, procedure, params)
12
+ end
13
+
14
+ def query_function(connection, function, params, columns: [])
15
+ fail ArgumentError if function.blank?
16
+
17
+ cols = columns.blank? ? '*' : columns.map { |c| PG::Connection.quote_ident(c) }.join(', ')
18
+ query = "SELECT #{cols} FROM #{quote_ident(function)}(#{bind_placeholders(params)})"
19
+ connection.exec_query(query, function, params)
20
+ end
21
+
22
+ private
23
+
24
+ def bind_placeholders(params)
25
+ (1..params.size).map { |n| "$#{n}" }.join(', ')
26
+ end
27
+
28
+ def quote_ident(...) = PG::Connection.quote_ident(...)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -15,7 +15,7 @@ module Sequent
15
15
  value = instance_variable_get(name.to_s)
16
16
  s += "#{name}=[#{value}], "
17
17
  end
18
- '{' + s.chomp(', ') + '}'
18
+ "{#{s.chomp(', ')}}"
19
19
  end
20
20
  end
21
21
  end
@@ -39,7 +39,7 @@ module Sequent
39
39
  if value.blank? && !(value.is_a?(TrueClass) || value.is_a?(FalseClass))
40
40
  nil
41
41
  else
42
- (value.is_a?(TrueClass) || value == 'true')
42
+ value.is_a?(TrueClass) || value == 'true'
43
43
  end
44
44
  end
45
45
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_record'
4
- require_relative './persistor'
4
+ require_relative 'persistor'
5
5
 
6
6
  module Sequent
7
7
  module Core
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
3
  require 'active_record'
5
4
  require 'csv'
6
- require_relative './persistor'
5
+ require_relative 'persistor'
7
6
 
8
7
  module Sequent
9
8
  module Core
@@ -177,7 +176,7 @@ module Sequent
177
176
  end
178
177
 
179
178
  indices.each do |record_class, indexed_columns|
180
- columns = indexed_columns.flatten(1).map(&:to_sym).to_set + default_indexed_columns
179
+ columns = indexed_columns.flatten(1).to_set(&:to_sym) + default_indexed_columns
181
180
  @record_index[record_class] = Index.new(columns & record_class.column_names.map(&:to_sym))
182
181
  end
183
182
 
@@ -358,7 +357,7 @@ module Sequent
358
357
 
359
358
  def clear
360
359
  @record_store.clear
361
- @record_index.values.each(&:clear)
360
+ @record_index.each_value(&:clear)
362
361
  end
363
362
 
364
363
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'helpers/message_handler'
4
- require_relative './persistors/active_record_persistor'
4
+ require_relative 'persistors/active_record_persistor'
5
5
 
6
6
  module Sequent
7
7
  module Core
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require_relative 'sequent_oj'
5
+ require_relative '../application_record'
6
+
7
+ module Sequent
8
+ module Core
9
+ class SnapshotRecord < Sequent::ApplicationRecord
10
+ include SerializesEvent
11
+
12
+ self.primary_key = %i[aggregate_id sequence_number]
13
+ self.table_name = 'snapshot_records'
14
+
15
+ belongs_to :stream_record, foreign_key: :aggregate_id, primary_key: :aggregate_id
16
+
17
+ validates_presence_of :aggregate_id, :sequence_number, :snapshot_json, :stream_record
18
+ validates_numericality_of :sequence_number, only_integer: true, greater_than: 0
19
+
20
+ private
21
+
22
+ def event_type
23
+ snapshot_type
24
+ end
25
+
26
+ def event_type=(type)
27
+ self.snapshot_type = type
28
+ end
29
+
30
+ def event_json
31
+ snapshot_json
32
+ end
33
+
34
+ def event_json=(json)
35
+ self.snapshot_json = json
36
+ end
37
+
38
+ def serialize_json?
39
+ json_column_type = self.class.columns_hash['snapshot_json'].sql_type_metadata.type
40
+ %i[json jsonb].exclude? json_column_type
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'sequent_oj'
4
+ require_relative 'helpers/pgsql_helpers'
5
+
6
+ module Sequent
7
+ module Core
8
+ module SnapshotStore
9
+ include Helpers::PgsqlHelpers
10
+
11
+ def store_snapshots(snapshots)
12
+ json = Sequent::Core::Oj.dump(
13
+ snapshots.map do |snapshot|
14
+ {
15
+ aggregate_id: snapshot.aggregate_id,
16
+ sequence_number: snapshot.sequence_number,
17
+ created_at: snapshot.created_at,
18
+ snapshot_type: snapshot.class.name,
19
+ snapshot_json: snapshot,
20
+ }
21
+ end,
22
+ )
23
+
24
+ call_procedure(connection, 'store_snapshots', [json])
25
+ end
26
+
27
+ def load_latest_snapshot(aggregate_id)
28
+ snapshot_hash = query_function(connection, 'load_latest_snapshot', [aggregate_id]).first
29
+ deserialize_event(snapshot_hash) unless snapshot_hash['aggregate_id'].nil?
30
+ end
31
+
32
+ # Deletes all snapshots for all aggregates
33
+ def delete_all_snapshots
34
+ call_procedure(connection, 'delete_all_snapshots', [Time.now])
35
+ end
36
+
37
+ # Deletes all snapshots for aggregate_id with a sequence_number lower than the specified sequence number.
38
+ def delete_snapshots_before(aggregate_id, sequence_number)
39
+ call_procedure(connection, 'delete_snapshots_before', [aggregate_id, sequence_number, Time.now])
40
+ end
41
+
42
+ # Marks an aggregate for snapshotting. Marked aggregates will be
43
+ # picked up by the background snapshotting task. Another way to
44
+ # mark aggregates for snapshotting is to pass the
45
+ # +EventStream#snapshot_outdated_at+ property to the
46
+ # +#store_events+ method as is done automatically by the
47
+ # +AggregateRepository+ based on the aggregate's
48
+ # +snapshot_threshold+.
49
+ def mark_aggregate_for_snapshotting(aggregate_id, snapshot_outdated_at: Time.now)
50
+ connection.exec_update(<<~EOS, 'mark_aggregate_for_snapshotting', [aggregate_id, snapshot_outdated_at])
51
+ INSERT INTO aggregates_that_need_snapshots AS row (aggregate_id, snapshot_outdated_at)
52
+ VALUES ($1, $2)
53
+ ON CONFLICT (aggregate_id) DO UPDATE
54
+ SET snapshot_outdated_at = LEAST(row.snapshot_outdated_at, EXCLUDED.snapshot_outdated_at),
55
+ snapshot_scheduled_at = NULL
56
+ EOS
57
+ end
58
+
59
+ # Stops snapshotting the specified aggregate. Any existing
60
+ # snapshots for this aggregate are also deleted.
61
+ def clear_aggregate_for_snapshotting(aggregate_id)
62
+ connection.exec_update(
63
+ 'DELETE FROM aggregates_that_need_snapshots WHERE aggregate_id = $1',
64
+ 'clear_aggregate_for_snapshotting',
65
+ [aggregate_id],
66
+ )
67
+ end
68
+
69
+ # Stops snapshotting all aggregates where the last event
70
+ # occurred before the indicated timestamp. Any existing
71
+ # snapshots for this aggregate are also deleted.
72
+ def clear_aggregates_for_snapshotting_with_last_event_before(timestamp)
73
+ connection.exec_update(<<~EOS, 'clear_aggregates_for_snapshotting_with_last_event_before', [timestamp])
74
+ DELETE FROM aggregates_that_need_snapshots s
75
+ WHERE NOT EXISTS (SELECT *
76
+ FROM aggregates a
77
+ JOIN events e ON (a.aggregate_id, a.events_partition_key) = (e.aggregate_id, e.partition_key)
78
+ WHERE a.aggregate_id = s.aggregate_id AND e.created_at >= $1)
79
+ EOS
80
+ end
81
+
82
+ ##
83
+ # Returns the ids of aggregates that need a new snapshot.
84
+ #
85
+ def aggregates_that_need_snapshots(last_aggregate_id, limit = 10)
86
+ query_function(
87
+ connection,
88
+ 'aggregates_that_need_snapshots',
89
+ [last_aggregate_id, limit],
90
+ columns: ['aggregate_id'],
91
+ )
92
+ .pluck('aggregate_id')
93
+ end
94
+
95
+ def select_aggregates_for_snapshotting(limit:, reschedule_snapshots_scheduled_before: nil)
96
+ query_function(
97
+ connection,
98
+ 'select_aggregates_for_snapshotting',
99
+ [limit, reschedule_snapshots_scheduled_before, Time.now],
100
+ columns: ['aggregate_id'],
101
+ ).pluck('aggregate_id')
102
+ end
103
+ end
104
+ end
105
+ end
@@ -4,38 +4,33 @@ require 'active_record'
4
4
 
5
5
  module Sequent
6
6
  module Core
7
- class EventStream
8
- attr_accessor :aggregate_type, :aggregate_id, :snapshot_threshold, :stream_record_id
9
-
10
- def initialize(aggregate_type:, aggregate_id:, snapshot_threshold: nil, stream_record_id: nil)
11
- @aggregate_type = aggregate_type
12
- @aggregate_id = aggregate_id
13
- @snapshot_threshold = snapshot_threshold
14
- @stream_record_id = stream_record_id
7
+ EventStream = Data.define(:aggregate_type, :aggregate_id, :events_partition_key, :snapshot_outdated_at) do
8
+ def initialize(aggregate_type:, aggregate_id:, events_partition_key: '', snapshot_outdated_at: nil)
9
+ super
15
10
  end
16
11
  end
17
12
 
18
13
  class StreamRecord < Sequent::ApplicationRecord
14
+ self.primary_key = %i[aggregate_id]
19
15
  self.table_name = 'stream_records'
16
+ self.ignored_columns = %w[snapshot_threshold]
20
17
 
21
18
  validates_presence_of :aggregate_type, :aggregate_id
22
- validates_numericality_of :snapshot_threshold, only_integer: true, greater_than: 0, allow_nil: true
23
19
 
24
- has_many :event_records
20
+ has_many :event_records, foreign_key: :aggregate_id, primary_key: :aggregate_id
25
21
 
26
22
  def event_stream
27
23
  EventStream.new(
28
- aggregate_type: aggregate_type,
29
- aggregate_id: aggregate_id,
30
- snapshot_threshold: snapshot_threshold,
31
- stream_record_id: id,
24
+ aggregate_type:,
25
+ aggregate_id:,
26
+ events_partition_key:,
32
27
  )
33
28
  end
34
29
 
35
30
  def event_stream=(data)
36
31
  self.aggregate_type = data.aggregate_type
37
32
  self.aggregate_id = data.aggregate_id
38
- self.snapshot_threshold = data.snapshot_threshold
33
+ self.events_partition_key = data.events_partition_key
39
34
  end
40
35
  end
41
36
  end
@@ -15,7 +15,7 @@ module Sequent
15
15
  # Running in dryrun mode, not committing anything.
16
16
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
17
  elapsed = ending - @starting
18
- count = @record_store.values.map(&:size).sum
18
+ count = @record_store.values.sum(&:size)
19
19
  Sequent.logger.info(
20
20
  "dryrun: processed #{count} records in #{elapsed.round(2)} s (#{(count / elapsed).round(2)} records/s)",
21
21
  )
@@ -9,7 +9,7 @@ module Sequent
9
9
  # This migration does not insert anything into the database, mainly usefull
10
10
  # for performance testing migrations.
11
11
  class ViewSchema < Migrations::ViewSchema
12
- def migrate_dryrun(regex:, group_exponent: 3, limit: nil, offset: nil)
12
+ def migrate_dryrun(regex:)
13
13
  persistor = DryRun::ReadOnlyReplayOptimizedPostgresPersistor.new
14
14
 
15
15
  projectors = Sequent::Core::Migratable.all.select { |p| p.replay_persistor.nil? && p.name.match(regex || /.*/) }
@@ -17,8 +17,7 @@ module Sequent
17
17
  Sequent.logger.info "Dry run using the following projectors: #{projectors.map(&:name).join(', ')}"
18
18
 
19
19
  starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
20
- groups = groups(group_exponent: group_exponent, limit: limit, offset: offset)
21
- replay!(persistor, projectors: projectors, groups: groups)
20
+ replay!(persistor, projectors:)
22
21
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
22
 
24
23
  Sequent.logger.info("Done migrate_dryrun for version #{Sequent.new_version} in #{ending - starting} s")
@@ -12,9 +12,10 @@ module Sequent
12
12
  end
13
13
 
14
14
  def execute
15
+ fail TargetAlreadyExists if File.exist?(path)
16
+
15
17
  make_directory
16
18
  copy_files
17
- rename_ruby_version
18
19
  rename_app_file
19
20
  replace_app_name
20
21
  end
@@ -27,12 +28,9 @@ module Sequent
27
28
 
28
29
  def copy_files
29
30
  FileUtils.copy_entry(File.expand_path('template_project', __dir__), path)
30
- end
31
-
32
- # Hidden files are by default excluded from gem build.
33
- # Therefor we need to rename the ruby-version to .ruby-version.
34
- def rename_ruby_version
35
- FileUtils.mv("#{path}/ruby-version", "#{path}/.ruby-version")
31
+ ['.ruby-version', 'db'].each do |file|
32
+ FileUtils.copy_entry(File.expand_path("../../../#{file}", __dir__), "#{path}/#{file}")
33
+ end
36
34
  end
37
35
 
38
36
  def rename_app_file
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddTemplateAggregate < Sequent::Command
2
4
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TemplateAggregateAdded < Sequent::Event
2
4
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TemplateAggregate < Sequent::AggregateRoot
2
4
  def initialize(command)
3
5
  super(command.aggregate_id)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TemplateAggregateCommandHandler < Sequent::CommandHandler
2
4
  on AddTemplateAggregate do |command|
3
5
  repository.add_aggregate TemplateAggregate.new(command)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'template_aggregate/commands'
2
4
  require_relative 'template_aggregate/events'
3
5
  require_relative 'template_aggregate/template_aggregate'
@@ -1,11 +1,13 @@
1
- source "https://rubygems.org"
2
- ruby "3.0.0"
1
+ # frozen_string_literal: true
3
2
 
3
+ source 'https://rubygems.org'
4
+ ruby file: '.ruby-version'
5
+
6
+ gem 'csv'
4
7
  gem 'rake'
5
- # let's use the latest and greatest
6
- gem 'sequent', git: 'https://github.com/zilverline/sequent'
8
+ gem 'sequent', '>= 8'
7
9
 
8
10
  group :test do
9
- gem 'rspec'
10
11
  gem 'database_cleaner'
12
+ gem 'rspec'
11
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['SEQUENT_ENV'] ||= 'development'
2
4
 
3
5
  require './my_app'
@@ -5,8 +7,8 @@ require 'sequent/rake/migration_tasks'
5
7
 
6
8
  Sequent::Rake::MigrationTasks.new.register_tasks!
7
9
 
8
- task "sequent:migrate:init" => [:db_connect]
10
+ task 'sequent:migrate:init' => [:db_connect]
9
11
 
10
- task "db_connect" do
12
+ task 'db_connect' do
11
13
  Sequent::Support::Database.connect!(ENV['SEQUENT_ENV'])
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../records/post_record'
2
4
  require_relative '../../lib/post/events'
3
5
 
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PostRecord < Sequent::ApplicationRecord
2
4
  end
@@ -1,13 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require './db/migrations'
2
4
 
3
5
  Sequent.configure do |config|
4
6
  config.migrations_class_name = 'Migrations'
5
-
6
- config.command_handlers = [
7
- PostCommandHandler.new
8
- ]
9
-
10
- config.event_handlers = [
11
- PostProjector.new
12
- ]
7
+ config.enable_autoregistration = true
13
8
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sequent/migrations/projectors'
2
4
 
3
5
  VIEW_SCHEMA_VERSION = 1
@@ -9,9 +11,7 @@ class Migrations < Sequent::Migrations::Projectors
9
11
 
10
12
  def self.versions
11
13
  {
12
- '1' => [
13
- PostProjector
14
- ]
14
+ '1' => [PostProjector],
15
15
  }
16
16
  end
17
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddPost < Sequent::Command
2
4
  attrs author: String, title: String, content: String
3
5
  validates_presence_of :author, :title, :content
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PostAdded < Sequent::Event
2
4
  end
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Post < Sequent::AggregateRoot
2
4
  def initialize(command)
3
5
  super(command.aggregate_id)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PostCommandHandler < Sequent::CommandHandler
2
4
  on AddPost do |command|
3
5
  repository.add_aggregate Post.new(command)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'post/commands'
2
4
  require_relative 'post/events'
3
5
  require_relative 'post/post'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sequent'
2
4
  require 'sequent/support'
3
5
  require 'erb'
@@ -7,5 +9,4 @@ require_relative 'app/projectors/post_projector'
7
9
  require_relative 'config/initializers/sequent'
8
10
 
9
11
  module MyApp
10
-
11
12
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require_relative '../../../app/projectors/post_projector'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require_relative '../../../lib/post'
3
5
 
@@ -9,12 +11,17 @@ describe PostCommandHandler do
9
11
  end
10
12
 
11
13
  it 'creates a post' do
12
- when_command AddPost.new(aggregate_id: aggregate_id, author: 'ben', title: 'My first blogpost', content: 'Hello World!')
14
+ when_command AddPost.new(
15
+ aggregate_id: aggregate_id,
16
+ author: 'ben',
17
+ title: 'My first blogpost',
18
+ content: 'Hello World!',
19
+ )
13
20
  then_events(
14
21
  PostAdded.new(aggregate_id: aggregate_id, sequence_number: 1),
15
22
  PostAuthorChanged.new(aggregate_id: aggregate_id, sequence_number: 2, author: 'ben'),
16
23
  PostTitleChanged,
17
- PostContentChanged
24
+ PostContentChanged,
18
25
  )
19
26
  end
20
27
  end