sequent 7.2.0 → 8.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 -4
  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 +2 -0
  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 +3 -1
  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 +71 -18
  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,10 +1,13 @@
1
- source "https://rubygems.org"
2
- ruby file: ".ruby-version"
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
- gem 'sequent'
8
+ gem 'sequent', '>= 8'
6
9
 
7
10
  group :test do
8
- gem 'rspec'
9
11
  gem 'database_cleaner'
12
+ gem 'rspec'
10
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require './db/migrations'
2
4
 
3
5
  Sequent.configure do |config|
@@ -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