10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
# File 'lib/separate_history/core.rb', line 10
def has_separate_history(options = {})
raise ArgumentError, "has_separate_history can not be called on an abstract class" if abstract_class?
raise ArgumentError, "Options :only and :except can not be used together" if options[:only] && options[:except]
valid_options = i[only except history_class_name events track_changes]
invalid_options = options.keys - valid_options
raise ArgumentError, "Invalid options: #{invalid_options.join(", ")}" if invalid_options.any?
options[:track_changes] = false if options[:track_changes].nil?
unless options[:track_changes].is_a?(TrueClass) || options[:track_changes].is_a?(FalseClass)
raise ArgumentError, "track_changes must be true or false"
end
supported_events = i[create update destroy]
if options[:events]
events = Array(options[:events])
invalid_events = events - supported_events
raise ArgumentError, "Invalid events: #{invalid_events.join(", ")}" if invalid_events.any?
end
cattr_accessor :separate_history_options
self.separate_history_options = options
class << self
def history_class
history_class_name = separate_history_options.fetch(:history_class_name, "#{name}History")
history_class_name.safe_constantize
end
def history_for(id, timestamp = Time.current)
history_class.where(original_id: id)
.where(history_updated_at: ..timestamp)
.order(history_updated_at: :desc, id: :desc)
.first
end
def history_as_of(id, timestamp)
history_for(id, timestamp)
end
def history_exists_for?(id)
history_class.where(original_id: id).exists?
end
def all_history_for(id)
history_class.where(original_id: id)
.order(history_updated_at: :asc, id: :asc)
end
def latest_history_for(id)
history_class.where(original_id: id)
.order(history_updated_at: :desc, id: :desc)
.first
end
def clear_history_for(id, force:)
raise ArgumentError, "Force must be true to clear history." unless force
history_class.where(original_id: id).delete_all
end
def clear_all_history(force:)
raise ArgumentError, "Force must be true to clear all history." unless force
history_class.delete_all
end
end
history_class_name = options.fetch(:history_class_name, "#{name}History")
history_table_name = history_class_name.tableize
association_name = name.demodulize.underscore.to_sym
has_many history_table_name.to_sym,
class_name: history_class_name,
foreign_key: :original_id,
inverse_of: association_name
alias_method :separate_histories, history_table_name.to_sym
history_class = history_class_name.safe_constantize
if history_class
unless history_class.reflect_on_association(association_name)
history_class.belongs_to association_name,
class_name: name,
foreign_key: :original_id,
inverse_of: history_table_name.to_sym,
optional: true
end
history_class.include SeparateHistory::History
end
events = Array(options[:events] || supported_events)
events.each do |event|
next unless supported_events.include?(event)
after_commit :"record_history_#{event}", on: event
end
include SeparateHistory::Model
SeparateHistory.track_model(self)
end
|