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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1be9b7cd983f181f719012a18922ca7e3107b03c4c663b25d960bbf1c7b1cc0
4
- data.tar.gz: 0c22a5c1a0b261e5cdb54a70f2c52a2bca38f25e7f97e095461b38e1257b8792
3
+ metadata.gz: 99bfa78c8c716d75bda5cec87447fc1bb066fd5fdf3e3c7c926e249a356f4f8d
4
+ data.tar.gz: b49cac2c1f50e481eeb0cb133a8e01c1bb8407b4a68b38047b27141b8192aa9e
5
5
  SHA512:
6
- metadata.gz: 364917d7bd4cc7a6b1e745e03e76301cf7d413a8d1929c1c24dfe7042f5bd4c32758feb51eeffbeb9918b0248875650e3b66cfe6072d9bdcdc4677b05306a25a
7
- data.tar.gz: 0ec4280ba169bad9bceb4c86450cbe8e089bba72fc03f2702311c2bf6f84a4dd66eb94cd85ec575760fcf8f31affb18caf89d409819e37d8752f6472c47b2d49
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) do
169
- @event_klass.insert_all!(hashes)
170
- end
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 = stream.joins(:event).where(@event_klass.table_name => { event_type: spec.with_types }) if spec.with_types?
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
- .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
- )
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
 
@@ -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
- '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'
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
- ['add_foreign_key_on_event_id_to_event_store_events_in_streams']
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(migration_version: migration_version)
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(migration_version: migration_version, data_type: database_adapter.data_type)
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 = Class.new(::Rails::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"] || './db'
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, 'migrate')
15
+ DatabaseTasks.migrations_paths = ENV["MIGRATIONS_PATH"] || File.join(db_dir, "migrate")
16
16
  end
17
17
 
18
- load 'active_record/railties/databases.rake'
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module ActiveRecord
5
- VERSION = "2.16.0"
5
+ VERSION = "2.17.0"
6
6
  end
7
7
  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
- require_relative "active_record/railtie" if defined?(Rails::Engine)
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.16.0
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: 2025-03-07 00:00:00.000000000 Z
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.16.0
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.16.0
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.2
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: []