historiographer 4.1.8 → 4.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28891ab31232a7eae8efccd015269069d992eb775259c6a6b9e738da1df69aed
4
- data.tar.gz: 7a794f1a26351b47d194a0b8d18eb0222d49e9dd7d2b6cdcf90467d94cea62b0
3
+ metadata.gz: e211bf451d7041d58702ebc28a4485eb3a073978aca72ef1933e9b5dcebc49fe
4
+ data.tar.gz: 20ab524186b29a4c3dd44553a62b8853cf46a82aae4f139d18d3178b045cb35a
5
5
  SHA512:
6
- metadata.gz: 16ee4305c1aa8c6f09a13b042185afceea10fd18c0a19f7dba360bcab65a7f1a270dce78e24f15f2a30806fa4f4bcdf5ea6c10ecb81060b3dca02724918d7f5e
7
- data.tar.gz: e66e7f2a31b5f65689f112ae6b43adeff5e286c42aeb6ed2ffc8931e1a75426dac3f4cb615b2fc06a537c74f070f4f2a245fe4668be8814b8382bd5084336651
6
+ metadata.gz: 055c01abfa33ab141ef48644ea38fa2a4052a9471d68519f89a659bbf45f83e37f66bb1aa847ba7014de9a46435af3cdced42d45ea122d25b70c409fdc312777
7
+ data.tar.gz: d7df91b900b2ea61b4e366b67972242d2882feadf8cfebbd8cd0d67875782535d267e084ec9cf044c835b4da757a0602b1ca8e75f6f27310faf4fe7b7bf69931
@@ -69,17 +69,6 @@ module Historiographer
69
69
  #
70
70
  scope :current, -> { where(history_ended_at: nil).order(id: :desc) }
71
71
 
72
- #
73
- # A History class will be linked to the user
74
- # that made the changes.
75
- #
76
- # E.g.
77
- #
78
- # RetailerProductHistory.first.user
79
- #
80
- # To use histories, a user class must be defined.
81
- #
82
- belongs_to :user, foreign_key: :history_user_id
83
72
 
84
73
  #
85
74
  # Historiographer is opinionated about how History classes
@@ -94,7 +83,20 @@ module Historiographer
94
83
 
95
84
  # Store the original class for method delegation
96
85
  class_variable_set(:@@original_class, foreign_class)
97
- class_variable_set(:@@method_map, {})
86
+
87
+ #
88
+ # A History class will be linked to the user
89
+ # that made the changes.
90
+ #
91
+ # E.g.
92
+ #
93
+ # RetailerProductHistory.first.user
94
+ #
95
+ # To use histories, a user class must be defined.
96
+ #
97
+ unless foreign_class.ancestors.include?(Historiographer::Silent)
98
+ belongs_to :user, foreign_key: :history_user_id
99
+ end
98
100
 
99
101
  # Add method_added hook to the original class
100
102
  foreign_class.singleton_class.class_eval do
@@ -120,8 +122,6 @@ module Historiographer
120
122
  # Skip if we've already defined this method in the history class
121
123
  return if foreign_class.history_class.method_defined?(method_name)
122
124
 
123
- # Define the method in the history class
124
- foreign_class.history_class.set_method_map(method_name, false)
125
125
  foreign_class.history_class.class_eval do
126
126
  define_method(method_name) do |*args, &block|
127
127
  forward_method(method_name, *args, &block)
@@ -244,24 +244,15 @@ module Historiographer
244
244
  raise "Cannot snapshot a history model!"
245
245
  end
246
246
 
247
- end
248
-
249
- class_methods do
250
- def method_added(method_name)
251
- set_method_map(method_name, true)
247
+ def is_history_class?
248
+ true
252
249
  end
253
250
 
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
251
+ end
259
252
 
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) || {}
253
+ class_methods do
254
+ def is_history_class?
255
+ true
265
256
  end
266
257
 
267
258
  def original_class
@@ -311,6 +302,7 @@ module Historiographer
311
302
  has_many assoc_name, scope, class_name: assoc_class_name, foreign_key: assoc_foreign_key, primary_key: history_foreign_key
312
303
  end
313
304
  end
305
+
314
306
  #
315
307
  # The foreign key to the primary class.
316
308
  #
@@ -343,20 +335,36 @@ module Historiographer
343
335
  def dummy_instance
344
336
  return @dummy_instance if @dummy_instance
345
337
 
338
+ # Only exclude history-specific columns
346
339
  cannot_keep_cols = %w(history_started_at history_ended_at history_user_id snapshot_id)
347
- cannot_keep_cols += [self.class.inheritance_column.to_sym] if self.original_class.sti_enabled?
348
340
  cannot_keep_cols += [self.class.history_foreign_key]
349
341
  cannot_keep_cols.map!(&:to_s)
350
342
 
351
343
  attrs = attributes.clone
352
344
  attrs[original_class.primary_key] = attrs[self.class.history_foreign_key]
353
345
 
354
- instance = original_class.find_or_initialize_by(original_class.primary_key => attrs[original_class.primary_key])
355
- instance.assign_attributes(attrs.except(*cannot_keep_cols))
346
+ if original_class.sti_enabled?
347
+ # Remove History suffix from type if present
348
+ attrs[original_class.inheritance_column] = attrs[original_class.inheritance_column]&.gsub(/History$/, '')
349
+ end
350
+
351
+ # Manually handle creating instance WITHOUT running find or initialize callbacks
352
+ # We will manually run callbacks below
353
+ # See: https://github.com/rails/rails/blob/95deab7b439abba23fdc4bd659116dab5dbe2606/activerecord/lib/active_record/core.rb#L487
354
+ #
355
+ attributes = original_class.attributes_builder.build_from_database(attrs.except(*cannot_keep_cols))
356
+ instance = original_class.allocate
357
+
358
+ # Set the internal attributes
359
+ instance.instance_variable_set(:@attributes, attributes)
360
+ instance.instance_variable_set(:@new_record, false)
361
+
362
+ # Initialize internal variables without triggering callbacks
363
+ instance.send(:init_internals)
356
364
 
357
365
  # Filter out any methods that are not overridden on the history class
358
366
  history_methods = self.class.instance_methods(false)
359
- history_class_location = Module.const_source_location(self.class.name).first
367
+ history_class_location = Module.const_source_location(self.class.name).first
360
368
  history_methods.select! do |method|
361
369
  self.class.instance_method(method).source_location.first == history_class_location
362
370
  end
@@ -381,15 +389,13 @@ module Historiographer
381
389
  end
382
390
  end
383
391
 
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
392
+ instance.instance_variable_set(:@_history_instance, self)
393
+
394
+ if instance.send(original_class.primary_key).present?
395
+ instance.run_callbacks(:find)
390
396
  end
397
+ instance.run_callbacks(:initialize)
391
398
 
392
- instance.instance_variable_set(:@_history_instance, self)
393
399
  @dummy_instance = instance
394
400
  end
395
401
 
@@ -1,3 +1,3 @@
1
1
  module Historiographer
2
- VERSION = "4.1.8"
2
+ VERSION = "4.1.10"
3
3
  end
@@ -332,6 +332,10 @@ module Historiographer
332
332
  history_class = self.class.history_class
333
333
  foreign_key = history_class.history_foreign_key
334
334
 
335
+ attrs = attrs.stringify_keys
336
+ allowed_columns = self.class.columns.map(&:name)
337
+ attrs.select! { |k,v| allowed_columns.include?(k) }.to_h
338
+
335
339
  now ||= UTC.now
336
340
  attrs.merge!(foreign_key => attrs['id'], history_started_at: now, history_user_id: history_user_id)
337
341
  attrs.merge!(snapshot_id: snapshot_id) if snapshot_id.present?
@@ -343,6 +347,7 @@ module Historiographer
343
347
  end
344
348
 
345
349
  attrs = attrs.except('id')
350
+ attrs.stringify_keys!
346
351
 
347
352
  attrs
348
353
  end
@@ -361,6 +366,23 @@ module Historiographer
361
366
  raise HistoryUserIdMissingError, 'history_user_id must be passed in order to save record with histories! If you are in a context with no history_user_id, explicitly call #save_without_user'
362
367
  end
363
368
 
369
+ def list_callbacks(klass)
370
+ callbacks = {}
371
+
372
+ [:create, :update, :save, :destroy, :validation].each do |callback_type|
373
+ chain = klass.send("_#{callback_type}_callbacks")
374
+ callbacks[callback_type] = chain.map do |callback|
375
+ {
376
+ name: callback.filter,
377
+ kind: callback.kind,
378
+ options: callback.options
379
+ }
380
+ end
381
+ end
382
+
383
+ callbacks
384
+ end
385
+
364
386
  #
365
387
  # Save a record of the most recent changes, with the current
366
388
  # time as history_started_at, and the provided user as history_user_id.
@@ -375,9 +397,11 @@ module Historiographer
375
397
  current_history = histories.where(history_ended_at: nil).order('id desc').limit(1).last
376
398
 
377
399
  if history_class.history_foreign_key.present? && history_class.present?
378
- history_class.create!(attrs).tap do |new_history|
379
- current_history.update!(history_ended_at: now) if current_history.present?
380
- end
400
+ result = history_class.insert_all([attrs])
401
+ inserted_id = result.rows.first.first if history_class.primary_key == 'id'
402
+ instance = history_class.find(inserted_id)
403
+ current_history.update_columns(history_ended_at: now) if current_history.present?
404
+ instance
381
405
  else
382
406
  raise 'Need foreign key and history class to save history!'
383
407
  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.8
4
+ version: 4.1.10
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-30 00:00:00.000000000 Z
11
+ date: 2024-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -247,7 +247,6 @@ files:
247
247
  - lib/historiographer.rb
248
248
  - lib/historiographer/configuration.rb
249
249
  - lib/historiographer/history.rb
250
- - lib/historiographer/history/instance_methods.rb
251
250
  - lib/historiographer/history_migration.rb
252
251
  - lib/historiographer/history_migration_mysql.rb
253
252
  - lib/historiographer/mysql_migration.rb
@@ -1,98 +0,0 @@
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