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.
- checksums.yaml +4 -4
- data/bin/sequent +6 -107
- data/db/sequent_8_migration.sql +120 -0
- data/db/sequent_pgsql.sql +416 -0
- data/db/sequent_schema.rb +11 -57
- data/db/sequent_schema_indexes.sql +37 -0
- data/db/sequent_schema_partitions.sql +34 -0
- data/db/sequent_schema_tables.sql +74 -0
- data/lib/sequent/cli/app.rb +132 -0
- data/lib/sequent/cli/sequent_8_migration.rb +180 -0
- data/lib/sequent/configuration.rb +11 -8
- data/lib/sequent/core/aggregate_repository.rb +2 -2
- data/lib/sequent/core/aggregate_root.rb +32 -9
- data/lib/sequent/core/aggregate_snapshotter.rb +8 -6
- data/lib/sequent/core/command_record.rb +27 -18
- data/lib/sequent/core/command_service.rb +2 -2
- data/lib/sequent/core/event_publisher.rb +1 -1
- data/lib/sequent/core/event_record.rb +37 -17
- data/lib/sequent/core/event_store.rb +101 -119
- data/lib/sequent/core/helpers/array_with_type.rb +1 -1
- data/lib/sequent/core/helpers/association_validator.rb +2 -2
- data/lib/sequent/core/helpers/attribute_support.rb +8 -8
- data/lib/sequent/core/helpers/equal_support.rb +3 -3
- data/lib/sequent/core/helpers/message_matchers/has_attrs.rb +2 -0
- data/lib/sequent/core/helpers/message_router.rb +2 -2
- data/lib/sequent/core/helpers/param_support.rb +1 -3
- data/lib/sequent/core/helpers/pgsql_helpers.rb +32 -0
- data/lib/sequent/core/helpers/string_support.rb +1 -1
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +1 -1
- data/lib/sequent/core/persistors/active_record_persistor.rb +1 -1
- data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +3 -4
- data/lib/sequent/core/projector.rb +1 -1
- data/lib/sequent/core/snapshot_record.rb +44 -0
- data/lib/sequent/core/snapshot_store.rb +105 -0
- data/lib/sequent/core/stream_record.rb +10 -15
- data/lib/sequent/dry_run/read_only_replay_optimized_postgres_persistor.rb +1 -1
- data/lib/sequent/dry_run/view_schema.rb +2 -3
- data/lib/sequent/generator/project.rb +5 -7
- data/lib/sequent/generator/template_aggregate/template_aggregate/commands.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/events.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate_command_handler.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate.rb +2 -0
- data/lib/sequent/generator/template_project/Gemfile +7 -5
- data/lib/sequent/generator/template_project/Rakefile +4 -2
- data/lib/sequent/generator/template_project/app/projectors/post_projector.rb +2 -0
- data/lib/sequent/generator/template_project/app/records/post_record.rb +2 -0
- data/lib/sequent/generator/template_project/config/initializers/sequent.rb +3 -8
- data/lib/sequent/generator/template_project/db/migrations.rb +3 -3
- data/lib/sequent/generator/template_project/lib/post/commands.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/events.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/post.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/post_command_handler.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post.rb +2 -0
- data/lib/sequent/generator/template_project/my_app.rb +2 -1
- data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +2 -0
- data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +9 -2
- data/lib/sequent/generator/template_project/spec/spec_helper.rb +4 -7
- data/lib/sequent/generator.rb +1 -1
- data/lib/sequent/internal/aggregate_type.rb +12 -0
- data/lib/sequent/internal/command_type.rb +12 -0
- data/lib/sequent/internal/event_type.rb +12 -0
- data/lib/sequent/internal/internal.rb +14 -0
- data/lib/sequent/internal/partitioned_aggregate.rb +26 -0
- data/lib/sequent/internal/partitioned_command.rb +16 -0
- data/lib/sequent/internal/partitioned_event.rb +29 -0
- data/lib/sequent/migrations/grouper.rb +90 -0
- data/lib/sequent/migrations/sequent_schema.rb +2 -1
- data/lib/sequent/migrations/view_schema.rb +76 -77
- data/lib/sequent/rake/migration_tasks.rb +49 -24
- data/lib/sequent/sequent.rb +1 -0
- data/lib/sequent/support/database.rb +20 -16
- data/lib/sequent/test/time_comparison.rb +1 -1
- data/lib/sequent/util/timer.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +102 -21
- data/lib/sequent/generator/template_project/db/sequent_schema.rb +0 -52
- 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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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.
|
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.
|
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.
|
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.
|
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 '
|
4
|
-
require_relative '
|
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?
|
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
|
@@ -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 '
|
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).
|
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.
|
360
|
+
@record_index.each_value(&:clear)
|
362
361
|
end
|
363
362
|
|
364
363
|
private
|
@@ -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
|
-
|
8
|
-
|
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
|
29
|
-
aggregate_id
|
30
|
-
|
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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,11 +1,13 @@
|
|
1
|
-
|
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
|
-
|
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
|
10
|
+
task 'sequent:migrate:init' => [:db_connect]
|
9
11
|
|
10
|
-
task
|
12
|
+
task 'db_connect' do
|
11
13
|
Sequent::Support::Database.connect!(ENV['SEQUENT_ENV'])
|
12
14
|
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
|
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(
|
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
|