ruby_event_store-active_record 2.16.0 → 2.17.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/lib/ruby_event_store/active_record/event_repository.rb +62 -12
- data/lib/ruby_event_store/active_record/event_repository_reader.rb +14 -15
- data/lib/ruby_event_store/active_record/generators/event_id_index_migration_generator.rb +2 -2
- data/lib/ruby_event_store/active_record/generators/foreign_key_on_event_id_migration_generator.rb +7 -5
- data/lib/ruby_event_store/active_record/generators/migration_generator.rb +4 -1
- data/lib/ruby_event_store/active_record/generators/rails_migration_generator.rb +1 -1
- data/lib/ruby_event_store/active_record/index_violation_detector.rb +1 -2
- data/lib/ruby_event_store/active_record/railtie.rb +9 -2
- data/lib/ruby_event_store/active_record/rake_task.rb +3 -3
- data/lib/ruby_event_store/active_record/skip_json_serialization.rb +24 -0
- data/lib/ruby_event_store/active_record/version.rb +1 -1
- data/lib/ruby_event_store/active_record/with_abstract_base_class.rb +2 -2
- data/lib/ruby_event_store/active_record.rb +6 -2
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99bfa78c8c716d75bda5cec87447fc1bb066fd5fdf3e3c7c926e249a356f4f8d
|
4
|
+
data.tar.gz: b49cac2c1f50e481eeb0cb133a8e01c1bb8407b4a68b38047b27141b8192aa9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6222089207f19b557c8109d9a10f57deee52a4a94604a1680fc11c40518b1055c207ec01d1e0e8ae192377600e0955a321146e44eaabcbca3a4d02ff68cfc162
|
7
|
+
data.tar.gz: 7d53c6633aa51964148215c1af44b398d93ce122b53d47d9505d8357f8672782e5bc2c6ebf2ed30110086d021faf76aa5449264f2aafb628af3268b0e6fb9730
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/array"
|
4
|
-
|
5
3
|
module RubyEventStore
|
6
4
|
module ActiveRecord
|
7
5
|
class EventRepository
|
@@ -9,12 +7,62 @@ module RubyEventStore
|
|
9
7
|
|
10
8
|
def initialize(model_factory: WithDefaultModels.new, serializer:)
|
11
9
|
@serializer = serializer
|
12
|
-
|
13
10
|
@event_klass, @stream_klass = model_factory.call
|
11
|
+
if serializer == NULL && json_data_type?
|
12
|
+
warn <<~MSG
|
13
|
+
The data or metadata column is of a JSON/B type and expects a JSON string.
|
14
|
+
|
15
|
+
Yet the repository serializer is configured as #{serializer} and it would not
|
16
|
+
produce the expected JSON string.
|
17
|
+
|
18
|
+
In ActiveRecord there's an implicit serialization to JSON for JSON/B column types
|
19
|
+
that made it work so far. This behaviour is unfortunately also a source of undesired
|
20
|
+
double serialization — first in the EventRepository, second in the ActiveRecord.
|
21
|
+
|
22
|
+
In the past we've advised workarounds that introduced configuration incosistency
|
23
|
+
with other data types and serialization formats, i.e. explicitly passing NULL serializer
|
24
|
+
just for the JSON/B data types.
|
25
|
+
|
26
|
+
As of now this special ActiveRecord behaviour is disabled. You should be using JSON
|
27
|
+
serializer back again:
|
28
|
+
|
29
|
+
RubyEventStore::ActiveRecord::EventRepository.new(serializer: JSON)
|
30
|
+
MSG
|
31
|
+
else
|
32
|
+
@event_klass.include(SkipJsonSerialization)
|
33
|
+
end
|
14
34
|
@repo_reader = EventRepositoryReader.new(@event_klass, @stream_klass, serializer)
|
15
35
|
@index_violation_detector = IndexViolationDetector.new(@event_klass.table_name, @stream_klass.table_name)
|
16
36
|
end
|
17
37
|
|
38
|
+
def rescue_from_double_json_serialization!
|
39
|
+
if serializer == JSON && json_data_type?
|
40
|
+
@repo_reader.instance_eval { alias __record__ record }
|
41
|
+
|
42
|
+
@repo_reader.define_singleton_method :unwrap do |column_name, payload|
|
43
|
+
if String === payload && payload.start_with?("\{")
|
44
|
+
warn "Double serialization of #{column_name} column detected"
|
45
|
+
serializer.load(payload)
|
46
|
+
else
|
47
|
+
payload
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@repo_reader.define_singleton_method :record do |record|
|
52
|
+
r = __record__(record)
|
53
|
+
|
54
|
+
Record.new(
|
55
|
+
event_id: r.event_id,
|
56
|
+
metadata: unwrap("metadata", r.metadata),
|
57
|
+
data: unwrap("data", r.data),
|
58
|
+
event_type: r.event_type,
|
59
|
+
timestamp: r.timestamp,
|
60
|
+
valid_at: r.valid_at,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
18
66
|
def append_to_stream(records, stream, expected_version)
|
19
67
|
return if records.empty?
|
20
68
|
|
@@ -99,7 +147,7 @@ module RubyEventStore
|
|
99
147
|
stream: stream.name,
|
100
148
|
position: compute_position(resolved_version, index),
|
101
149
|
event_id: event_id,
|
102
|
-
created_at: Time.now.utc
|
150
|
+
created_at: Time.now.utc,
|
103
151
|
}
|
104
152
|
end
|
105
153
|
@stream_klass.insert_all!(in_stream) unless stream.global?
|
@@ -129,7 +177,7 @@ module RubyEventStore
|
|
129
177
|
metadata: serialized_record.metadata,
|
130
178
|
event_type: serialized_record.event_type,
|
131
179
|
created_at: record.timestamp,
|
132
|
-
valid_at: optimize_timestamp(record.valid_at, record.timestamp)
|
180
|
+
valid_at: optimize_timestamp(record.valid_at, record.timestamp),
|
133
181
|
}
|
134
182
|
end
|
135
183
|
|
@@ -139,7 +187,7 @@ module RubyEventStore
|
|
139
187
|
data: serialized_record.data,
|
140
188
|
metadata: serialized_record.metadata,
|
141
189
|
event_type: serialized_record.event_type,
|
142
|
-
valid_at: optimize_timestamp(record.valid_at, record.timestamp)
|
190
|
+
valid_at: optimize_timestamp(record.valid_at, record.timestamp),
|
143
191
|
}
|
144
192
|
end
|
145
193
|
|
@@ -152,9 +200,7 @@ module RubyEventStore
|
|
152
200
|
end
|
153
201
|
|
154
202
|
def link_to_stream_(event_ids, stream, expected_version)
|
155
|
-
(
|
156
|
-
event_ids - @event_klass.where(event_id: event_ids).pluck(:event_id)
|
157
|
-
).each { |id| raise EventNotFound.new(id) }
|
203
|
+
(event_ids - @event_klass.where(event_id: event_ids).pluck(:event_id)).each { |id| raise EventNotFound.new(id) }
|
158
204
|
add_to_stream(event_ids, stream, expected_version)
|
159
205
|
end
|
160
206
|
|
@@ -165,9 +211,13 @@ module RubyEventStore
|
|
165
211
|
hashes << insert_hash(record, record.serialize(serializer))
|
166
212
|
event_ids << record.event_id
|
167
213
|
end
|
168
|
-
add_to_stream(event_ids, stream, expected_version)
|
169
|
-
|
170
|
-
|
214
|
+
add_to_stream(event_ids, stream, expected_version) { @event_klass.insert_all!(hashes) }
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def json_data_type?
|
220
|
+
%i[data metadata].any? { |attr| @event_klass.column_for_attribute(attr).type.start_with?("json") }
|
171
221
|
end
|
172
222
|
end
|
173
223
|
end
|
@@ -98,7 +98,8 @@ module RubyEventStore
|
|
98
98
|
else
|
99
99
|
stream = @stream_klass.includes(:event).where(stream: spec.stream.name)
|
100
100
|
stream = stream.where(event_id: spec.with_ids) if spec.with_ids?
|
101
|
-
stream =
|
101
|
+
stream =
|
102
|
+
stream.joins(:event).where(@event_klass.table_name => { event_type: spec.with_types }) if spec.with_types?
|
102
103
|
stream = stream.joins(:event).order(as_at(spec)) if spec.time_sort_by_as_at?
|
103
104
|
stream = stream.joins(:event).order(as_of(spec)) if spec.time_sort_by_as_of?
|
104
105
|
stream = stream.order(id: order(spec))
|
@@ -137,7 +138,7 @@ module RubyEventStore
|
|
137
138
|
start_offset_condition(
|
138
139
|
specification,
|
139
140
|
@stream_klass.find_by!(event_id: specification.start, stream: specification.stream.name),
|
140
|
-
@stream_klass.table_name
|
141
|
+
@stream_klass.table_name,
|
141
142
|
)
|
142
143
|
rescue ::ActiveRecord::RecordNotFound
|
143
144
|
raise EventNotFound.new(specification.start)
|
@@ -147,7 +148,7 @@ module RubyEventStore
|
|
147
148
|
stop_offset_condition(
|
148
149
|
specification,
|
149
150
|
@stream_klass.find_by!(event_id: specification.stop, stream: specification.stream.name),
|
150
|
-
@stream_klass.table_name
|
151
|
+
@stream_klass.table_name,
|
151
152
|
)
|
152
153
|
rescue ::ActiveRecord::RecordNotFound
|
153
154
|
raise EventNotFound.new(specification.stop)
|
@@ -157,7 +158,7 @@ module RubyEventStore
|
|
157
158
|
start_offset_condition(
|
158
159
|
specification,
|
159
160
|
@event_klass.find_by!(event_id: specification.start),
|
160
|
-
@event_klass.table_name
|
161
|
+
@event_klass.table_name,
|
161
162
|
)
|
162
163
|
rescue ::ActiveRecord::RecordNotFound
|
163
164
|
raise EventNotFound.new(specification.start)
|
@@ -167,7 +168,7 @@ module RubyEventStore
|
|
167
168
|
stop_offset_condition(
|
168
169
|
specification,
|
169
170
|
@event_klass.find_by!(event_id: specification.stop),
|
170
|
-
@event_klass.table_name
|
171
|
+
@event_klass.table_name,
|
171
172
|
)
|
172
173
|
rescue ::ActiveRecord::RecordNotFound
|
173
174
|
raise EventNotFound.new(specification.stop)
|
@@ -208,16 +209,14 @@ module RubyEventStore
|
|
208
209
|
def record(record)
|
209
210
|
record = record.event if @stream_klass === record
|
210
211
|
|
211
|
-
SerializedRecord
|
212
|
-
.
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
)
|
220
|
-
.deserialize(serializer)
|
212
|
+
SerializedRecord.new(
|
213
|
+
event_id: record.event_id,
|
214
|
+
metadata: record.metadata,
|
215
|
+
data: record.data,
|
216
|
+
event_type: record.event_type,
|
217
|
+
timestamp: record.created_at.iso8601(TIMESTAMP_PRECISION),
|
218
|
+
valid_at: (record.valid_at || record.created_at).iso8601(TIMESTAMP_PRECISION),
|
219
|
+
).deserialize(serializer)
|
221
220
|
end
|
222
221
|
end
|
223
222
|
|
@@ -22,8 +22,8 @@ module RubyEventStore
|
|
22
22
|
def migration_template
|
23
23
|
ERB.new(
|
24
24
|
File.read(
|
25
|
-
File.join(absolute_path("./templates"), "add_event_id_index_to_event_store_events_in_streams_template.erb")
|
26
|
-
)
|
25
|
+
File.join(absolute_path("./templates"), "add_event_id_index_to_event_store_events_in_streams_template.erb"),
|
26
|
+
),
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
data/lib/ruby_event_store/active_record/generators/foreign_key_on_event_id_migration_generator.rb
CHANGED
@@ -16,12 +16,12 @@ module RubyEventStore
|
|
16
16
|
def each_migration(database_adapter, &block)
|
17
17
|
case database_adapter
|
18
18
|
when DatabaseAdapter::PostgreSQL
|
19
|
-
[
|
20
|
-
|
21
|
-
|
19
|
+
%w[
|
20
|
+
add_foreign_key_on_event_id_to_event_store_events_in_streams
|
21
|
+
validate_add_foreign_key_on_event_id_to_event_store_events_in_streams
|
22
22
|
]
|
23
23
|
else
|
24
|
-
[
|
24
|
+
["add_foreign_key_on_event_id_to_event_store_events_in_streams"]
|
25
25
|
end.each.with_index(&block)
|
26
26
|
end
|
27
27
|
|
@@ -30,7 +30,9 @@ module RubyEventStore
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def migration_code(database_adapter, migration_name)
|
33
|
-
migration_template(template_root(database_adapter), migration_name).result_with_hash(
|
33
|
+
migration_template(template_root(database_adapter), migration_name).result_with_hash(
|
34
|
+
migration_version: migration_version,
|
35
|
+
)
|
34
36
|
end
|
35
37
|
|
36
38
|
def migration_template(template_root, name)
|
@@ -19,7 +19,10 @@ module RubyEventStore
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def migration_code(database_adapter)
|
22
|
-
migration_template(template_root(database_adapter), "create_event_store_events").result_with_hash(
|
22
|
+
migration_template(template_root(database_adapter), "create_event_store_events").result_with_hash(
|
23
|
+
migration_version: migration_version,
|
24
|
+
data_type: database_adapter.data_type,
|
25
|
+
)
|
23
26
|
end
|
24
27
|
|
25
28
|
def template_root(database_adapter)
|
@@ -19,7 +19,7 @@ module RubyEventStore
|
|
19
19
|
type: :string,
|
20
20
|
default: "binary",
|
21
21
|
desc:
|
22
|
-
"Configure the data type for `data` and `meta data` fields in migration (options: #{DatabaseAdapter::PostgreSQL.new.supported_data_types.join(", ")})"
|
22
|
+
"Configure the data type for `data` and `meta data` fields in migration (options: #{DatabaseAdapter::PostgreSQL.new.supported_data_types.join(", ")})",
|
23
23
|
)
|
24
24
|
|
25
25
|
def initialize(*args)
|
@@ -10,8 +10,7 @@ module RubyEventStore
|
|
10
10
|
@mysql8_pkey_error = "for key '#{event_store_events}.index_#{event_store_events}_on_event_id'".freeze
|
11
11
|
@mysql5_index_error = "for key 'index_#{event_store_events_in_streams}_on_stream_and_event_id'".freeze
|
12
12
|
@mysql8_index_error =
|
13
|
-
"for key '#{event_store_events_in_streams}.index_#{event_store_events_in_streams}_on_stream_and_event_id'"
|
14
|
-
.freeze
|
13
|
+
"for key '#{event_store_events_in_streams}.index_#{event_store_events_in_streams}_on_stream_and_event_id'".freeze
|
15
14
|
@sqlite3_pkey_error = "constraint failed: #{event_store_events}.event_id".freeze
|
16
15
|
@sqlite3_index_error =
|
17
16
|
"constraint failed: #{event_store_events_in_streams}.stream, #{event_store_events_in_streams}.event_id".freeze
|
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
module RubyEventStore
|
4
4
|
module ActiveRecord
|
5
|
-
Railtie
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
initializer "ruby_event_store-active_record" do
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
8
|
+
require_relative "../active_record/skip_json_serialization"
|
9
|
+
require_relative "../active_record/event"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
6
13
|
end
|
7
|
-
end
|
14
|
+
end
|
@@ -6,13 +6,13 @@ load "ruby_event_store/active_record/tasks/migration_tasks.rake"
|
|
6
6
|
|
7
7
|
include ActiveRecord::Tasks
|
8
8
|
|
9
|
-
db_dir = ENV["DATABASE_DIR"] ||
|
9
|
+
db_dir = ENV["DATABASE_DIR"] || "./db"
|
10
10
|
|
11
11
|
task :environment do
|
12
12
|
connection = ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
|
13
13
|
DatabaseTasks.env = connection.db_config.env_name
|
14
14
|
DatabaseTasks.db_dir = db_dir
|
15
|
-
DatabaseTasks.migrations_paths = ENV["MIGRATIONS_PATH"] || File.join(db_dir,
|
15
|
+
DatabaseTasks.migrations_paths = ENV["MIGRATIONS_PATH"] || File.join(db_dir, "migrate")
|
16
16
|
end
|
17
17
|
|
18
|
-
load
|
18
|
+
load "active_record/railties/databases.rake"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
module RubyEventStore
|
6
|
+
module ActiveRecord
|
7
|
+
module SkipJsonSerialization
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
skip_json_serialization = ->(cast_type) do
|
11
|
+
%i[json jsonb].include?(cast_type.type) ? ActiveModel::Type::Value.new : cast_type
|
12
|
+
end
|
13
|
+
|
14
|
+
if ::ActiveRecord.version >= Gem::Version.new("7.2.0")
|
15
|
+
class_methods { define_method(:hook_attribute_type) { |name, cast_type| skip_json_serialization[cast_type] } }
|
16
|
+
else
|
17
|
+
included do
|
18
|
+
attribute :data, skip_json_serialization
|
19
|
+
attribute :metadata, skip_json_serialization
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -22,7 +22,7 @@ module RubyEventStore
|
|
22
22
|
Class.new(@base_klass) do
|
23
23
|
self.primary_key = :id
|
24
24
|
self.table_name = "event_store_events"
|
25
|
-
end
|
25
|
+
end,
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
@@ -33,7 +33,7 @@ module RubyEventStore
|
|
33
33
|
self.primary_key = :id
|
34
34
|
self.table_name = "event_store_events_in_streams"
|
35
35
|
belongs_to :event, primary_key: :event_id, class_name: "Event_#{instance_id}"
|
36
|
-
end
|
36
|
+
end,
|
37
37
|
)
|
38
38
|
end
|
39
39
|
end
|
@@ -7,7 +7,6 @@ require_relative "active_record/generators/event_id_index_migration_generator"
|
|
7
7
|
require_relative "active_record/generators/rails_event_id_index_migration_generator"
|
8
8
|
require_relative "active_record/generators/foreign_key_on_event_id_migration_generator"
|
9
9
|
require_relative "active_record/generators/rails_foreign_key_on_event_id_migration_generator"
|
10
|
-
require_relative "active_record/event"
|
11
10
|
require_relative "active_record/with_default_models"
|
12
11
|
require_relative "active_record/with_abstract_base_class"
|
13
12
|
require_relative "active_record/event_repository"
|
@@ -16,4 +15,9 @@ require_relative "active_record/event_repository_reader"
|
|
16
15
|
require_relative "active_record/index_violation_detector"
|
17
16
|
require_relative "active_record/pg_linearized_event_repository"
|
18
17
|
require_relative "active_record/version"
|
19
|
-
|
18
|
+
if defined?(Rails::Engine)
|
19
|
+
require_relative "active_record/railtie"
|
20
|
+
else
|
21
|
+
require_relative "active_record/skip_json_serialization"
|
22
|
+
require_relative "active_record/event"
|
23
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_event_store-active_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arkency
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: ruby_event_store
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - '='
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 2.
|
18
|
+
version: 2.17.0
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - '='
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: 2.
|
25
|
+
version: 2.17.0
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activerecord
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- lib/ruby_event_store/active_record/pg_linearized_event_repository.rb
|
76
76
|
- lib/ruby_event_store/active_record/railtie.rb
|
77
77
|
- lib/ruby_event_store/active_record/rake_task.rb
|
78
|
+
- lib/ruby_event_store/active_record/skip_json_serialization.rb
|
78
79
|
- lib/ruby_event_store/active_record/tasks/migration_tasks.rake
|
79
80
|
- lib/ruby_event_store/active_record/version.rb
|
80
81
|
- lib/ruby_event_store/active_record/with_abstract_base_class.rb
|
@@ -102,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
103
|
- !ruby/object:Gem::Version
|
103
104
|
version: '0'
|
104
105
|
requirements: []
|
105
|
-
rubygems_version: 3.6.
|
106
|
+
rubygems_version: 3.6.9
|
106
107
|
specification_version: 4
|
107
108
|
summary: Persistent event repository implementation for RubyEventStore based on ActiveRecord
|
108
109
|
test_files: []
|