historiographer 4.1.7 → 4.1.8
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/historiographer/history/instance_methods.rb +98 -0
- data/lib/historiographer/history.rb +87 -24
- data/lib/historiographer/version.rb +1 -1
- data/lib/historiographer.rb +4 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28891ab31232a7eae8efccd015269069d992eb775259c6a6b9e738da1df69aed
|
4
|
+
data.tar.gz: 7a794f1a26351b47d194a0b8d18eb0222d49e9dd7d2b6cdcf90467d94cea62b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16ee4305c1aa8c6f09a13b042185afceea10fd18c0a19f7dba360bcab65a7f1a270dce78e24f15f2a30806fa4f4bcdf5ea6c10ecb81060b3dca02724918d7f5e
|
7
|
+
data.tar.gz: e66e7f2a31b5f65689f112ae6b43adeff5e286c42aeb6ed2ffc8931e1a75426dac3f4cb615b2fc06a537c74f070f4f2a245fe4668be8814b8382bd5084336651
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Historiographer
|
2
|
+
module History
|
3
|
+
module InstanceMethods
|
4
|
+
def destroy
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
def destroy!
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(*args, **kwargs)
|
13
|
+
if persisted? && (changes.keys - %w(history_ended_at snapshot_id)).any?
|
14
|
+
false
|
15
|
+
else
|
16
|
+
super(*args, **kwargs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def save!(*args, **kwargs)
|
21
|
+
if persisted? && (changes.keys - %w(history_ended_at snapshot_id)).any?
|
22
|
+
false
|
23
|
+
else
|
24
|
+
super(*args, **kwargs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def snapshot
|
29
|
+
raise "Cannot snapshot a history model!"
|
30
|
+
end
|
31
|
+
|
32
|
+
def original_class
|
33
|
+
self.class.original_class
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def dummy_instance
|
39
|
+
return @dummy_instance if @dummy_instance
|
40
|
+
|
41
|
+
cannot_keep_cols = %w(history_started_at history_ended_at history_user_id snapshot_id)
|
42
|
+
cannot_keep_cols += [self.class.inheritance_column.to_sym] if self.original_class.sti_enabled?
|
43
|
+
cannot_keep_cols += [self.class.history_foreign_key]
|
44
|
+
cannot_keep_cols.map!(&:to_s)
|
45
|
+
|
46
|
+
attrs = attributes.clone
|
47
|
+
attrs[original_class.primary_key] = attrs[self.class.history_foreign_key]
|
48
|
+
|
49
|
+
instance = original_class.find_or_initialize_by(original_class.primary_key => attrs[original_class.primary_key])
|
50
|
+
instance.assign_attributes(attrs.except(*cannot_keep_cols))
|
51
|
+
|
52
|
+
# Create a module to hold methods from the history class
|
53
|
+
history_methods_module = Module.new
|
54
|
+
|
55
|
+
# Get methods defined directly in the history class, excluding baseline methods
|
56
|
+
history_methods = self.class.instance_methods(false) - baseline_methods
|
57
|
+
|
58
|
+
history_methods.each do |method_name|
|
59
|
+
next if instance.singleton_class.method_defined?(method_name)
|
60
|
+
|
61
|
+
method = self.class.instance_method(method_name)
|
62
|
+
history_methods_module.define_method(method_name) do |*args, &block|
|
63
|
+
method.bind(self.instance_variable_get(:@_history_instance)).call(*args, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
instance.singleton_class.prepend(history_methods_module)
|
68
|
+
|
69
|
+
self.class.reflect_on_all_associations.each do |reflection|
|
70
|
+
instance.singleton_class.class_eval do
|
71
|
+
define_method(reflection.name) do |*args, &block|
|
72
|
+
history_instance = instance.instance_variable_get(:@_history_instance)
|
73
|
+
history_instance.send(reflection.name, *args, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
instance.singleton_class.class_eval do
|
79
|
+
define_method(:class) do
|
80
|
+
history_instance = instance.instance_variable_get(:@_history_instance)
|
81
|
+
history_instance.class
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
instance.instance_variable_set(:@_history_instance, self)
|
86
|
+
@dummy_instance = instance
|
87
|
+
end
|
88
|
+
|
89
|
+
def forward_method(method_name, *args, &block)
|
90
|
+
if method_name == :class || method_name == 'class'
|
91
|
+
self.class
|
92
|
+
else
|
93
|
+
dummy_instance.send(method_name, *args, &block)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -61,7 +61,7 @@ module Historiographer
|
|
61
61
|
extend ActiveSupport::Concern
|
62
62
|
|
63
63
|
included do |base|
|
64
|
-
clear_validators!
|
64
|
+
clear_validators! if respond_to?(:clear_validators!)
|
65
65
|
#
|
66
66
|
# A History class (e.g. RetailerProductHistory) will gain
|
67
67
|
# access to a current scope, returning
|
@@ -94,6 +94,7 @@ module Historiographer
|
|
94
94
|
|
95
95
|
# Store the original class for method delegation
|
96
96
|
class_variable_set(:@@original_class, foreign_class)
|
97
|
+
class_variable_set(:@@method_map, {})
|
97
98
|
|
98
99
|
# Add method_added hook to the original class
|
99
100
|
foreign_class.singleton_class.class_eval do
|
@@ -102,7 +103,6 @@ module Historiographer
|
|
102
103
|
alias_method :original_method_added, :method_added
|
103
104
|
end
|
104
105
|
|
105
|
-
method_map = Hash.new(0)
|
106
106
|
define_method(:method_added) do |method_name|
|
107
107
|
# Skip if we're already in the process of defining a method
|
108
108
|
return if Thread.current[:defining_historiographer_method]
|
@@ -121,6 +121,7 @@ module Historiographer
|
|
121
121
|
return if foreign_class.history_class.method_defined?(method_name)
|
122
122
|
|
123
123
|
# Define the method in the history class
|
124
|
+
foreign_class.history_class.set_method_map(method_name, false)
|
124
125
|
foreign_class.history_class.class_eval do
|
125
126
|
define_method(method_name) do |*args, &block|
|
126
127
|
forward_method(method_name, *args, &block)
|
@@ -239,9 +240,30 @@ module Historiographer
|
|
239
240
|
define_history_association(association)
|
240
241
|
end
|
241
242
|
|
243
|
+
def snapshot
|
244
|
+
raise "Cannot snapshot a history model!"
|
245
|
+
end
|
246
|
+
|
242
247
|
end
|
243
248
|
|
244
249
|
class_methods do
|
250
|
+
def method_added(method_name)
|
251
|
+
set_method_map(method_name, true)
|
252
|
+
end
|
253
|
+
|
254
|
+
def set_method_map(method_name, is_overridden)
|
255
|
+
mm = method_map
|
256
|
+
mm[method_name.to_sym] = is_overridden
|
257
|
+
class_variable_set(:@@method_map, mm)
|
258
|
+
end
|
259
|
+
|
260
|
+
def method_map
|
261
|
+
unless class_variable_defined?(:@@method_map)
|
262
|
+
class_variable_set(:@@method_map, {})
|
263
|
+
end
|
264
|
+
class_variable_get(:@@method_map) || {}
|
265
|
+
end
|
266
|
+
|
245
267
|
def original_class
|
246
268
|
unless class_variable_defined?(:@@original_class)
|
247
269
|
class_variable_set(:@@original_class, self.name.gsub(/History$/, '').constantize)
|
@@ -255,38 +277,38 @@ module Historiographer
|
|
255
277
|
association = original_class.reflect_on_association(association)
|
256
278
|
end
|
257
279
|
assoc_name = association.name
|
280
|
+
assoc_module = association.active_record.module_parent
|
258
281
|
assoc_history_class_name = "#{association.class_name}History"
|
259
|
-
assoc_foreign_key = association.foreign_key
|
260
282
|
|
261
|
-
|
262
|
-
|
283
|
+
begin
|
284
|
+
assoc_module.const_get(assoc_history_class_name)
|
285
|
+
assoc_history_class_name = "#{assoc_module}::#{assoc_history_class_name}" unless assoc_history_class_name.match?(Regexp.new("#{assoc_module}::"))
|
286
|
+
rescue
|
287
|
+
end
|
288
|
+
|
289
|
+
assoc_foreign_key = association.foreign_key
|
263
290
|
|
264
291
|
# Skip through associations to history classes to avoid infinite loops
|
265
292
|
return if association.class_name.end_with?('History')
|
266
293
|
|
267
|
-
#
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
#
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
294
|
+
# Always use the history class if it exists
|
295
|
+
assoc_class = assoc_history_class_name.safe_constantize || OpenStruct.new(name: association.class_name)
|
296
|
+
assoc_class_name = assoc_class.name
|
297
|
+
|
298
|
+
# Define the scope to filter by snapshot_id for history associations
|
299
|
+
scope = if assoc_class_name.match?(/History/)
|
300
|
+
->(history_instance) { where(snapshot_id: history_instance.snapshot_id) }
|
301
|
+
else
|
302
|
+
->(history_instance) { all }
|
303
|
+
end
|
276
304
|
|
277
305
|
case association.macro
|
278
306
|
when :belongs_to
|
279
|
-
belongs_to assoc_name,
|
280
|
-
where(snapshot_id: history_instance.snapshot_id)
|
281
|
-
}, class_name: assoc_history_class_name, foreign_key: assoc_foreign_key, primary_key: assoc_foreign_key
|
307
|
+
belongs_to assoc_name, scope, class_name: assoc_class_name, foreign_key: assoc_foreign_key, primary_key: assoc_foreign_key
|
282
308
|
when :has_one
|
283
|
-
has_one assoc_name,
|
284
|
-
where(snapshot_id: history_instance.snapshot_id)
|
285
|
-
}, class_name: assoc_history_class_name, foreign_key: assoc_foreign_key, primary_key: history_foreign_key
|
309
|
+
has_one assoc_name, scope, class_name: assoc_class_name, foreign_key: assoc_foreign_key, primary_key: history_foreign_key
|
286
310
|
when :has_many
|
287
|
-
has_many assoc_name,
|
288
|
-
where(snapshot_id: history_instance.snapshot_id)
|
289
|
-
}, class_name: assoc_history_class_name, foreign_key: assoc_foreign_key, primary_key: history_foreign_key
|
311
|
+
has_many assoc_name, scope, class_name: assoc_class_name, foreign_key: assoc_foreign_key, primary_key: history_foreign_key
|
290
312
|
end
|
291
313
|
end
|
292
314
|
#
|
@@ -331,11 +353,52 @@ module Historiographer
|
|
331
353
|
|
332
354
|
instance = original_class.find_or_initialize_by(original_class.primary_key => attrs[original_class.primary_key])
|
333
355
|
instance.assign_attributes(attrs.except(*cannot_keep_cols))
|
356
|
+
|
357
|
+
# Filter out any methods that are not overridden on the history class
|
358
|
+
history_methods = self.class.instance_methods(false)
|
359
|
+
history_class_location = Module.const_source_location(self.class.name).first
|
360
|
+
history_methods.select! do |method|
|
361
|
+
self.class.instance_method(method).source_location.first == history_class_location
|
362
|
+
end
|
363
|
+
|
364
|
+
history_methods.each do |method_name|
|
365
|
+
instance.singleton_class.class_eval do
|
366
|
+
define_method(method_name) do |*args, &block|
|
367
|
+
history_instance = instance.instance_variable_get(:@_history_instance)
|
368
|
+
history_instance.send(method_name, *args, &block)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# For each association in the history class
|
374
|
+
self.class.reflect_on_all_associations.each do |reflection|
|
375
|
+
# Define a method that forwards to the history association
|
376
|
+
instance.singleton_class.class_eval do
|
377
|
+
define_method(reflection.name) do |*args, &block|
|
378
|
+
history_instance = instance.instance_variable_get(:@_history_instance)
|
379
|
+
history_instance.send(reflection.name, *args, &block)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Override class method to return history class
|
385
|
+
instance.singleton_class.class_eval do
|
386
|
+
define_method(:class) do
|
387
|
+
history_instance = instance.instance_variable_get(:@_history_instance)
|
388
|
+
history_instance.class
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
instance.instance_variable_set(:@_history_instance, self)
|
334
393
|
@dummy_instance = instance
|
335
394
|
end
|
336
395
|
|
337
396
|
def forward_method(method_name, *args, &block)
|
338
|
-
|
397
|
+
if method_name == :class || method_name == 'class'
|
398
|
+
self.class
|
399
|
+
else
|
400
|
+
dummy_instance.send(method_name, *args, &block)
|
401
|
+
end
|
339
402
|
end
|
340
403
|
end
|
341
404
|
end
|
data/lib/historiographer.rb
CHANGED
@@ -216,21 +216,25 @@ module Historiographer
|
|
216
216
|
base.singleton_class.prepend(Module.new do
|
217
217
|
def belongs_to(name, scope = nil, **options, &extension)
|
218
218
|
super
|
219
|
+
return if is_history_class?
|
219
220
|
history_class.define_history_association(name)
|
220
221
|
end
|
221
222
|
|
222
223
|
def has_one(name, scope = nil, **options, &extension)
|
223
224
|
super
|
225
|
+
return if is_history_class?
|
224
226
|
history_class.define_history_association(name)
|
225
227
|
end
|
226
228
|
|
227
229
|
def has_many(name, scope = nil, **options, &extension)
|
228
230
|
super
|
231
|
+
return if is_history_class?
|
229
232
|
history_class.define_history_association(name)
|
230
233
|
end
|
231
234
|
|
232
235
|
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
|
233
236
|
super
|
237
|
+
return if is_history_class?
|
234
238
|
history_class.define_history_association(name)
|
235
239
|
end
|
236
240
|
end)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: historiographer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- brettshollenberger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -220,6 +220,20 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: zeitwerk
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
223
237
|
description: Append-only histories + chained snapshots of your ActiveRecord tables
|
224
238
|
email: brett.shollenberger@gmail.com
|
225
239
|
executables: []
|
@@ -233,6 +247,7 @@ files:
|
|
233
247
|
- lib/historiographer.rb
|
234
248
|
- lib/historiographer/configuration.rb
|
235
249
|
- lib/historiographer/history.rb
|
250
|
+
- lib/historiographer/history/instance_methods.rb
|
236
251
|
- lib/historiographer/history_migration.rb
|
237
252
|
- lib/historiographer/history_migration_mysql.rb
|
238
253
|
- lib/historiographer/mysql_migration.rb
|