mongoid-history 0.8.3 → 0.8.5
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/.coveralls.yml +1 -1
- data/.document +5 -5
- data/.github/workflows/test.yml +72 -0
- data/.gitignore +46 -46
- data/.rspec +2 -2
- data/.rubocop.yml +6 -6
- data/.rubocop_todo.yml +99 -99
- data/CHANGELOG.md +173 -163
- data/CONTRIBUTING.md +117 -118
- data/Dangerfile +1 -1
- data/Gemfile +49 -40
- data/LICENSE.txt +20 -20
- data/README.md +609 -608
- data/RELEASING.md +66 -67
- data/Rakefile +24 -24
- data/UPGRADING.md +53 -53
- data/lib/mongoid/history/attributes/base.rb +72 -72
- data/lib/mongoid/history/attributes/create.rb +45 -45
- data/lib/mongoid/history/attributes/destroy.rb +34 -34
- data/lib/mongoid/history/attributes/update.rb +104 -104
- data/lib/mongoid/history/options.rb +177 -177
- data/lib/mongoid/history/trackable.rb +588 -583
- data/lib/mongoid/history/tracker.rb +247 -247
- data/lib/mongoid/history/version.rb +5 -5
- data/lib/mongoid/history.rb +77 -77
- data/lib/mongoid-history.rb +1 -1
- data/mongoid-history.gemspec +25 -25
- data/perf/benchmark_modified_attributes_for_create.rb +65 -65
- data/perf/gc_suite.rb +21 -21
- data/spec/integration/embedded_in_polymorphic_spec.rb +112 -112
- data/spec/integration/integration_spec.rb +976 -976
- data/spec/integration/multi_relation_spec.rb +47 -47
- data/spec/integration/multiple_trackers_spec.rb +68 -68
- data/spec/integration/nested_embedded_documents_spec.rb +64 -64
- data/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb +124 -124
- data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +115 -115
- data/spec/integration/subclasses_spec.rb +47 -47
- data/spec/integration/track_history_order_spec.rb +84 -84
- data/spec/integration/validation_failure_spec.rb +76 -76
- data/spec/spec_helper.rb +32 -30
- data/spec/support/error_helpers.rb +7 -0
- data/spec/support/mongoid.rb +11 -11
- data/spec/support/mongoid_history.rb +12 -12
- data/spec/unit/attributes/base_spec.rb +141 -141
- data/spec/unit/attributes/create_spec.rb +342 -342
- data/spec/unit/attributes/destroy_spec.rb +228 -228
- data/spec/unit/attributes/update_spec.rb +342 -342
- data/spec/unit/callback_options_spec.rb +165 -165
- data/spec/unit/embedded_methods_spec.rb +87 -87
- data/spec/unit/history_spec.rb +58 -58
- data/spec/unit/my_instance_methods_spec.rb +555 -555
- data/spec/unit/options_spec.rb +365 -365
- data/spec/unit/singleton_methods_spec.rb +406 -406
- data/spec/unit/store/default_store_spec.rb +11 -11
- data/spec/unit/store/request_store_spec.rb +13 -13
- data/spec/unit/trackable_spec.rb +1057 -987
- data/spec/unit/tracker_spec.rb +190 -190
- metadata +9 -7
- data/.travis.yml +0 -36
@@ -1,247 +1,247 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module History
|
3
|
-
module Tracker
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do
|
7
|
-
include Mongoid::Document
|
8
|
-
include Mongoid::Timestamps
|
9
|
-
attr_writer :trackable
|
10
|
-
|
11
|
-
field :association_chain, type: Array, default: []
|
12
|
-
field :modified, type: Hash, default: {}
|
13
|
-
field :original, type: Hash, default: {}
|
14
|
-
field :version, type: Integer
|
15
|
-
field :action, type: String
|
16
|
-
field :scope, type: String
|
17
|
-
modifier_options = {
|
18
|
-
class_name: Mongoid::History.modifier_class_name
|
19
|
-
}
|
20
|
-
modifier_options[:optional] = true if Mongoid::Compatibility::Version.mongoid6_or_newer?
|
21
|
-
belongs_to :modifier, modifier_options
|
22
|
-
|
23
|
-
index(scope: 1)
|
24
|
-
index(association_chain: 1)
|
25
|
-
|
26
|
-
Mongoid::History.tracker_class_name ||= name.tableize.singularize.to_sym
|
27
|
-
end
|
28
|
-
|
29
|
-
def undo!(modifier = nil)
|
30
|
-
if action.to_sym == :destroy
|
31
|
-
re_create
|
32
|
-
elsif action.to_sym == :create
|
33
|
-
re_destroy
|
34
|
-
elsif Mongoid::Compatibility::Version.mongoid3?
|
35
|
-
trackable.update_attributes!(undo_attr(modifier), without_protection: true)
|
36
|
-
else
|
37
|
-
trackable.update_attributes!(undo_attr(modifier))
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def redo!(modifier = nil)
|
42
|
-
if action.to_sym == :destroy
|
43
|
-
re_destroy
|
44
|
-
elsif action.to_sym == :create
|
45
|
-
re_create
|
46
|
-
elsif Mongoid::Compatibility::Version.mongoid3?
|
47
|
-
trackable.update_attributes!(redo_attr(modifier), without_protection: true)
|
48
|
-
else
|
49
|
-
trackable.update_attributes!(redo_attr(modifier))
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def undo_attr(modifier)
|
54
|
-
undo_hash = affected.easy_unmerge(modified)
|
55
|
-
undo_hash.easy_merge!(original)
|
56
|
-
modifier_field = trackable.history_trackable_options[:modifier_field]
|
57
|
-
undo_hash[modifier_field] = modifier if modifier_field
|
58
|
-
(modified.keys - undo_hash.keys).each do |k|
|
59
|
-
undo_hash[k] = nil
|
60
|
-
end
|
61
|
-
localize_keys(undo_hash)
|
62
|
-
end
|
63
|
-
|
64
|
-
def redo_attr(modifier)
|
65
|
-
redo_hash = affected.easy_unmerge(original)
|
66
|
-
redo_hash.easy_merge!(modified)
|
67
|
-
modifier_field = trackable.history_trackable_options[:modifier_field]
|
68
|
-
redo_hash[modifier_field] = modifier if modifier_field
|
69
|
-
localize_keys(redo_hash)
|
70
|
-
end
|
71
|
-
|
72
|
-
def trackable_root
|
73
|
-
@trackable_root ||= trackable_parents_and_trackable.first
|
74
|
-
end
|
75
|
-
|
76
|
-
def trackable
|
77
|
-
@trackable ||= trackable_parents_and_trackable.last
|
78
|
-
end
|
79
|
-
|
80
|
-
def trackable_parents
|
81
|
-
@trackable_parents ||= trackable_parents_and_trackable[0, -1]
|
82
|
-
end
|
83
|
-
|
84
|
-
def trackable_parent
|
85
|
-
@trackable_parent ||= trackable_parents_and_trackable[-2]
|
86
|
-
end
|
87
|
-
|
88
|
-
# Outputs a :from, :to hash for each affected field. Intentionally excludes fields
|
89
|
-
# which are not tracked, even if there are tracked values for such fields
|
90
|
-
# present in the database.
|
91
|
-
#
|
92
|
-
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
93
|
-
# { field_1: {to: new_val}, field_2: {from: old_val, to: new_val} }
|
94
|
-
def tracked_changes
|
95
|
-
@tracked_changes ||= (modified.keys | original.keys).inject(HashWithIndifferentAccess.new) do |h, k|
|
96
|
-
h[k] = { from: original[k], to: modified[k] }.delete_if { |_, vv| vv.nil? }
|
97
|
-
h
|
98
|
-
end.delete_if { |k, v| v.blank? || !trackable_parent_class.tracked?(k) }
|
99
|
-
end
|
100
|
-
|
101
|
-
# Outputs summary of edit actions performed: :add, :modify, :remove, or :array.
|
102
|
-
# Does deep comparison of arrays. Useful for creating human-readable representations
|
103
|
-
# of the history tracker. Considers changing a value to 'blank' to be a removal.
|
104
|
-
#
|
105
|
-
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
106
|
-
# { add: { field_1: new_val, ... },
|
107
|
-
# modify: { field_2: {from: old_val, to: new_val}, ... },
|
108
|
-
# remove: { field_3: old_val },
|
109
|
-
# array: { field_4: {add: ['foo', 'bar'], remove: ['baz']} } }
|
110
|
-
def tracked_edits
|
111
|
-
return @tracked_edits if @tracked_edits
|
112
|
-
@tracked_edits = HashWithIndifferentAccess.new
|
113
|
-
|
114
|
-
tracked_changes.each do |k, v|
|
115
|
-
next if v[:from].blank? && v[:to].blank?
|
116
|
-
|
117
|
-
if trackable_parent_class.tracked_embeds_many?(k)
|
118
|
-
prepare_tracked_edits_for_embeds_many(k, v)
|
119
|
-
elsif v[:from].blank?
|
120
|
-
@tracked_edits[:add] ||= {}
|
121
|
-
@tracked_edits[:add][k] = v[:to]
|
122
|
-
elsif v[:to].blank?
|
123
|
-
@tracked_edits[:remove] ||= {}
|
124
|
-
@tracked_edits[:remove][k] = v[:from]
|
125
|
-
elsif v[:from].is_a?(Array) && v[:to].is_a?(Array)
|
126
|
-
@tracked_edits[:array] ||= {}
|
127
|
-
old_values = v[:from] - v[:to]
|
128
|
-
new_values = v[:to] - v[:from]
|
129
|
-
@tracked_edits[:array][k] = { add: new_values, remove: old_values }.delete_if { |_, vv| vv.blank? }
|
130
|
-
else
|
131
|
-
@tracked_edits[:modify] ||= {}
|
132
|
-
@tracked_edits[:modify][k] = v
|
133
|
-
end
|
134
|
-
end
|
135
|
-
@tracked_edits
|
136
|
-
end
|
137
|
-
|
138
|
-
# Similar to #tracked_changes, but contains only a single value for each
|
139
|
-
# affected field:
|
140
|
-
# - :create and :update return the modified values
|
141
|
-
# - :destroy returns original values
|
142
|
-
# Included for legacy compatibility.
|
143
|
-
#
|
144
|
-
# @deprecated
|
145
|
-
#
|
146
|
-
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
147
|
-
# { field_1: value, field_2: value }
|
148
|
-
def affected
|
149
|
-
target = action.to_sym == :destroy ? :from : :to
|
150
|
-
@affected ||= tracked_changes.inject(HashWithIndifferentAccess.new) do |h, (k, v)|
|
151
|
-
h[k] = v[target]
|
152
|
-
h
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# Returns the class of the trackable, irrespective of whether the trackable object
|
157
|
-
# has been destroyed.
|
158
|
-
#
|
159
|
-
# @return [ Class ] the class of the trackable
|
160
|
-
def trackable_parent_class
|
161
|
-
association_chain.first['name'].constantize
|
162
|
-
end
|
163
|
-
|
164
|
-
private
|
165
|
-
|
166
|
-
def re_create
|
167
|
-
association_chain.length > 1 ? create_on_parent : create_standalone
|
168
|
-
end
|
169
|
-
|
170
|
-
def re_destroy
|
171
|
-
trackable.destroy
|
172
|
-
end
|
173
|
-
|
174
|
-
def create_standalone
|
175
|
-
restored = trackable_parent_class.new(localize_keys(original))
|
176
|
-
restored.id = original['_id']
|
177
|
-
restored.save!
|
178
|
-
end
|
179
|
-
|
180
|
-
def create_on_parent
|
181
|
-
name = association_chain.last['name']
|
182
|
-
|
183
|
-
if trackable_parent.class.embeds_one?(name)
|
184
|
-
trackable_parent._create_relation(name, localize_keys(original))
|
185
|
-
elsif trackable_parent.class.embeds_many?(name)
|
186
|
-
trackable_parent._get_relation(name).create!(localize_keys(original))
|
187
|
-
else
|
188
|
-
raise 'This should never happen. Please report bug!'
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def trackable_parents_and_trackable
|
193
|
-
@trackable_parents_and_trackable ||= traverse_association_chain
|
194
|
-
end
|
195
|
-
|
196
|
-
def traverse_association_chain
|
197
|
-
chain = association_chain.dup
|
198
|
-
doc = nil
|
199
|
-
documents = []
|
200
|
-
loop do
|
201
|
-
node = chain.shift
|
202
|
-
name = node['name']
|
203
|
-
doc = if doc.nil?
|
204
|
-
# root association. First element of the association chain
|
205
|
-
# unscoped is added to remove any default_scope defined in model
|
206
|
-
klass = name.classify.constantize
|
207
|
-
klass.unscoped.where(_id: node['id']).first
|
208
|
-
elsif doc.class.embeds_one?(name)
|
209
|
-
doc._get_relation(name)
|
210
|
-
elsif doc.class.embeds_many?(name)
|
211
|
-
doc._get_relation(name).unscoped.where(_id: node['id']).first
|
212
|
-
else
|
213
|
-
relation_klass = doc.class.relation_class_of(name) if doc
|
214
|
-
relation_klass ||= 'nil'
|
215
|
-
raise "Unexpected relation for field '#{name}': #{relation_klass}. This should never happen. Please report bug."
|
216
|
-
end
|
217
|
-
documents << doc
|
218
|
-
break if chain.empty?
|
219
|
-
end
|
220
|
-
documents
|
221
|
-
end
|
222
|
-
|
223
|
-
def localize_keys(hash)
|
224
|
-
klass = association_chain.first['name'].constantize
|
225
|
-
if klass.respond_to?(:localized_fields)
|
226
|
-
klass.localized_fields.keys.each do |name|
|
227
|
-
hash["#{name}_translations"] = hash.delete(name) if hash[name].present?
|
228
|
-
end
|
229
|
-
end
|
230
|
-
hash
|
231
|
-
end
|
232
|
-
|
233
|
-
def prepare_tracked_edits_for_embeds_many(key, value)
|
234
|
-
@tracked_edits[:embeds_many] ||= {}
|
235
|
-
value[:from] ||= []
|
236
|
-
value[:to] ||= []
|
237
|
-
modify_ids = value[:from].map { |vv| vv['_id'] }.compact & value[:to].map { |vv| vv['_id'] }.compact
|
238
|
-
modify_values = modify_ids.map { |id| { from: value[:from].detect { |vv| vv['_id'] == id }, to: value[:to].detect { |vv| vv['_id'] == id } } }
|
239
|
-
modify_values.delete_if { |vv| vv[:from] == vv[:to] }
|
240
|
-
ignore_values = modify_values.map { |vv| [vv[:from], vv[:to]] }.flatten
|
241
|
-
old_values = value[:from] - value[:to] - ignore_values
|
242
|
-
new_values = value[:to] - value[:from] - ignore_values
|
243
|
-
@tracked_edits[:embeds_many][key] = { add: new_values, remove: old_values, modify: modify_values }.delete_if { |_, vv| vv.blank? }
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
1
|
+
module Mongoid
|
2
|
+
module History
|
3
|
+
module Tracker
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Mongoid::Document
|
8
|
+
include Mongoid::Timestamps
|
9
|
+
attr_writer :trackable
|
10
|
+
|
11
|
+
field :association_chain, type: Array, default: []
|
12
|
+
field :modified, type: Hash, default: {}
|
13
|
+
field :original, type: Hash, default: {}
|
14
|
+
field :version, type: Integer
|
15
|
+
field :action, type: String
|
16
|
+
field :scope, type: String
|
17
|
+
modifier_options = {
|
18
|
+
class_name: Mongoid::History.modifier_class_name
|
19
|
+
}
|
20
|
+
modifier_options[:optional] = true if Mongoid::Compatibility::Version.mongoid6_or_newer?
|
21
|
+
belongs_to :modifier, modifier_options
|
22
|
+
|
23
|
+
index(scope: 1)
|
24
|
+
index(association_chain: 1)
|
25
|
+
|
26
|
+
Mongoid::History.tracker_class_name ||= name.tableize.singularize.to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
def undo!(modifier = nil)
|
30
|
+
if action.to_sym == :destroy
|
31
|
+
re_create
|
32
|
+
elsif action.to_sym == :create
|
33
|
+
re_destroy
|
34
|
+
elsif Mongoid::Compatibility::Version.mongoid3?
|
35
|
+
trackable.update_attributes!(undo_attr(modifier), without_protection: true)
|
36
|
+
else
|
37
|
+
trackable.update_attributes!(undo_attr(modifier))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def redo!(modifier = nil)
|
42
|
+
if action.to_sym == :destroy
|
43
|
+
re_destroy
|
44
|
+
elsif action.to_sym == :create
|
45
|
+
re_create
|
46
|
+
elsif Mongoid::Compatibility::Version.mongoid3?
|
47
|
+
trackable.update_attributes!(redo_attr(modifier), without_protection: true)
|
48
|
+
else
|
49
|
+
trackable.update_attributes!(redo_attr(modifier))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def undo_attr(modifier)
|
54
|
+
undo_hash = affected.easy_unmerge(modified)
|
55
|
+
undo_hash.easy_merge!(original)
|
56
|
+
modifier_field = trackable.history_trackable_options[:modifier_field]
|
57
|
+
undo_hash[modifier_field] = modifier if modifier_field
|
58
|
+
(modified.keys - undo_hash.keys).each do |k|
|
59
|
+
undo_hash[k] = nil
|
60
|
+
end
|
61
|
+
localize_keys(undo_hash)
|
62
|
+
end
|
63
|
+
|
64
|
+
def redo_attr(modifier)
|
65
|
+
redo_hash = affected.easy_unmerge(original)
|
66
|
+
redo_hash.easy_merge!(modified)
|
67
|
+
modifier_field = trackable.history_trackable_options[:modifier_field]
|
68
|
+
redo_hash[modifier_field] = modifier if modifier_field
|
69
|
+
localize_keys(redo_hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
def trackable_root
|
73
|
+
@trackable_root ||= trackable_parents_and_trackable.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def trackable
|
77
|
+
@trackable ||= trackable_parents_and_trackable.last
|
78
|
+
end
|
79
|
+
|
80
|
+
def trackable_parents
|
81
|
+
@trackable_parents ||= trackable_parents_and_trackable[0, -1]
|
82
|
+
end
|
83
|
+
|
84
|
+
def trackable_parent
|
85
|
+
@trackable_parent ||= trackable_parents_and_trackable[-2]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Outputs a :from, :to hash for each affected field. Intentionally excludes fields
|
89
|
+
# which are not tracked, even if there are tracked values for such fields
|
90
|
+
# present in the database.
|
91
|
+
#
|
92
|
+
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
93
|
+
# { field_1: {to: new_val}, field_2: {from: old_val, to: new_val} }
|
94
|
+
def tracked_changes
|
95
|
+
@tracked_changes ||= (modified.keys | original.keys).inject(HashWithIndifferentAccess.new) do |h, k|
|
96
|
+
h[k] = { from: original[k], to: modified[k] }.delete_if { |_, vv| vv.nil? }
|
97
|
+
h
|
98
|
+
end.delete_if { |k, v| v.blank? || !trackable_parent_class.tracked?(k) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Outputs summary of edit actions performed: :add, :modify, :remove, or :array.
|
102
|
+
# Does deep comparison of arrays. Useful for creating human-readable representations
|
103
|
+
# of the history tracker. Considers changing a value to 'blank' to be a removal.
|
104
|
+
#
|
105
|
+
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
106
|
+
# { add: { field_1: new_val, ... },
|
107
|
+
# modify: { field_2: {from: old_val, to: new_val}, ... },
|
108
|
+
# remove: { field_3: old_val },
|
109
|
+
# array: { field_4: {add: ['foo', 'bar'], remove: ['baz']} } }
|
110
|
+
def tracked_edits
|
111
|
+
return @tracked_edits if @tracked_edits
|
112
|
+
@tracked_edits = HashWithIndifferentAccess.new
|
113
|
+
|
114
|
+
tracked_changes.each do |k, v|
|
115
|
+
next if v[:from].blank? && v[:to].blank?
|
116
|
+
|
117
|
+
if trackable_parent_class.tracked_embeds_many?(k)
|
118
|
+
prepare_tracked_edits_for_embeds_many(k, v)
|
119
|
+
elsif v[:from].blank?
|
120
|
+
@tracked_edits[:add] ||= {}
|
121
|
+
@tracked_edits[:add][k] = v[:to]
|
122
|
+
elsif v[:to].blank?
|
123
|
+
@tracked_edits[:remove] ||= {}
|
124
|
+
@tracked_edits[:remove][k] = v[:from]
|
125
|
+
elsif v[:from].is_a?(Array) && v[:to].is_a?(Array)
|
126
|
+
@tracked_edits[:array] ||= {}
|
127
|
+
old_values = v[:from] - v[:to]
|
128
|
+
new_values = v[:to] - v[:from]
|
129
|
+
@tracked_edits[:array][k] = { add: new_values, remove: old_values }.delete_if { |_, vv| vv.blank? }
|
130
|
+
else
|
131
|
+
@tracked_edits[:modify] ||= {}
|
132
|
+
@tracked_edits[:modify][k] = v
|
133
|
+
end
|
134
|
+
end
|
135
|
+
@tracked_edits
|
136
|
+
end
|
137
|
+
|
138
|
+
# Similar to #tracked_changes, but contains only a single value for each
|
139
|
+
# affected field:
|
140
|
+
# - :create and :update return the modified values
|
141
|
+
# - :destroy returns original values
|
142
|
+
# Included for legacy compatibility.
|
143
|
+
#
|
144
|
+
# @deprecated
|
145
|
+
#
|
146
|
+
# @return [ HashWithIndifferentAccess ] a change set in the format:
|
147
|
+
# { field_1: value, field_2: value }
|
148
|
+
def affected
|
149
|
+
target = action.to_sym == :destroy ? :from : :to
|
150
|
+
@affected ||= tracked_changes.inject(HashWithIndifferentAccess.new) do |h, (k, v)|
|
151
|
+
h[k] = v[target]
|
152
|
+
h
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the class of the trackable, irrespective of whether the trackable object
|
157
|
+
# has been destroyed.
|
158
|
+
#
|
159
|
+
# @return [ Class ] the class of the trackable
|
160
|
+
def trackable_parent_class
|
161
|
+
association_chain.first['name'].constantize
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def re_create
|
167
|
+
association_chain.length > 1 ? create_on_parent : create_standalone
|
168
|
+
end
|
169
|
+
|
170
|
+
def re_destroy
|
171
|
+
trackable.destroy
|
172
|
+
end
|
173
|
+
|
174
|
+
def create_standalone
|
175
|
+
restored = trackable_parent_class.new(localize_keys(original))
|
176
|
+
restored.id = original['_id']
|
177
|
+
restored.save!
|
178
|
+
end
|
179
|
+
|
180
|
+
def create_on_parent
|
181
|
+
name = association_chain.last['name']
|
182
|
+
|
183
|
+
if trackable_parent.class.embeds_one?(name)
|
184
|
+
trackable_parent._create_relation(name, localize_keys(original))
|
185
|
+
elsif trackable_parent.class.embeds_many?(name)
|
186
|
+
trackable_parent._get_relation(name).create!(localize_keys(original))
|
187
|
+
else
|
188
|
+
raise 'This should never happen. Please report bug!'
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def trackable_parents_and_trackable
|
193
|
+
@trackable_parents_and_trackable ||= traverse_association_chain
|
194
|
+
end
|
195
|
+
|
196
|
+
def traverse_association_chain
|
197
|
+
chain = association_chain.dup
|
198
|
+
doc = nil
|
199
|
+
documents = []
|
200
|
+
loop do
|
201
|
+
node = chain.shift
|
202
|
+
name = node['name']
|
203
|
+
doc = if doc.nil?
|
204
|
+
# root association. First element of the association chain
|
205
|
+
# unscoped is added to remove any default_scope defined in model
|
206
|
+
klass = name.classify.constantize
|
207
|
+
klass.unscoped.where(_id: node['id']).first
|
208
|
+
elsif doc.class.embeds_one?(name)
|
209
|
+
doc._get_relation(name)
|
210
|
+
elsif doc.class.embeds_many?(name)
|
211
|
+
doc._get_relation(name).unscoped.where(_id: node['id']).first
|
212
|
+
else
|
213
|
+
relation_klass = doc.class.relation_class_of(name) if doc
|
214
|
+
relation_klass ||= 'nil'
|
215
|
+
raise "Unexpected relation for field '#{name}': #{relation_klass}. This should never happen. Please report bug."
|
216
|
+
end
|
217
|
+
documents << doc
|
218
|
+
break if chain.empty?
|
219
|
+
end
|
220
|
+
documents
|
221
|
+
end
|
222
|
+
|
223
|
+
def localize_keys(hash)
|
224
|
+
klass = association_chain.first['name'].constantize
|
225
|
+
if klass.respond_to?(:localized_fields)
|
226
|
+
klass.localized_fields.keys.each do |name|
|
227
|
+
hash["#{name}_translations"] = hash.delete(name) if hash[name].present?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
hash
|
231
|
+
end
|
232
|
+
|
233
|
+
def prepare_tracked_edits_for_embeds_many(key, value)
|
234
|
+
@tracked_edits[:embeds_many] ||= {}
|
235
|
+
value[:from] ||= []
|
236
|
+
value[:to] ||= []
|
237
|
+
modify_ids = value[:from].map { |vv| vv['_id'] }.compact & value[:to].map { |vv| vv['_id'] }.compact
|
238
|
+
modify_values = modify_ids.map { |id| { from: value[:from].detect { |vv| vv['_id'] == id }, to: value[:to].detect { |vv| vv['_id'] == id } } }
|
239
|
+
modify_values.delete_if { |vv| vv[:from] == vv[:to] }
|
240
|
+
ignore_values = modify_values.map { |vv| [vv[:from], vv[:to]] }.flatten
|
241
|
+
old_values = value[:from] - value[:to] - ignore_values
|
242
|
+
new_values = value[:to] - value[:from] - ignore_values
|
243
|
+
@tracked_edits[:embeds_many][key] = { add: new_values, remove: old_values, modify: modify_values }.delete_if { |_, vv| vv.blank? }
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module History
|
3
|
-
VERSION = '0.8.
|
4
|
-
end
|
5
|
-
end
|
1
|
+
module Mongoid
|
2
|
+
module History
|
3
|
+
VERSION = '0.8.5'.freeze
|
4
|
+
end
|
5
|
+
end
|
data/lib/mongoid/history.rb
CHANGED
@@ -1,77 +1,77 @@
|
|
1
|
-
require 'easy_diff'
|
2
|
-
require 'mongoid/compatibility'
|
3
|
-
require 'mongoid/history/attributes/base'
|
4
|
-
require 'mongoid/history/attributes/create'
|
5
|
-
require 'mongoid/history/attributes/update'
|
6
|
-
require 'mongoid/history/attributes/destroy'
|
7
|
-
require 'mongoid/history/options'
|
8
|
-
require 'mongoid/history/version'
|
9
|
-
require 'mongoid/history/tracker'
|
10
|
-
require 'mongoid/history/trackable'
|
11
|
-
|
12
|
-
module Mongoid
|
13
|
-
module History
|
14
|
-
GLOBAL_TRACK_HISTORY_FLAG = 'mongoid_history_trackable_enabled'.freeze
|
15
|
-
|
16
|
-
class << self
|
17
|
-
attr_accessor :tracker_class_name
|
18
|
-
attr_accessor :trackable_settings
|
19
|
-
attr_accessor :modifier_class_name
|
20
|
-
attr_accessor :current_user_method
|
21
|
-
|
22
|
-
def disable
|
23
|
-
original_flag = store[GLOBAL_TRACK_HISTORY_FLAG]
|
24
|
-
store[GLOBAL_TRACK_HISTORY_FLAG] = false
|
25
|
-
yield if block_given?
|
26
|
-
ensure
|
27
|
-
store[GLOBAL_TRACK_HISTORY_FLAG] = original_flag if block_given?
|
28
|
-
end
|
29
|
-
|
30
|
-
def enable
|
31
|
-
original_flag = store[GLOBAL_TRACK_HISTORY_FLAG]
|
32
|
-
store[GLOBAL_TRACK_HISTORY_FLAG] = true
|
33
|
-
yield if block_given?
|
34
|
-
ensure
|
35
|
-
store[GLOBAL_TRACK_HISTORY_FLAG] = original_flag if block_given?
|
36
|
-
end
|
37
|
-
|
38
|
-
alias disable! disable
|
39
|
-
alias enable! enable
|
40
|
-
|
41
|
-
def enabled?
|
42
|
-
store[GLOBAL_TRACK_HISTORY_FLAG] != false
|
43
|
-
end
|
44
|
-
|
45
|
-
def store
|
46
|
-
defined?(RequestStore) ? RequestStore.store : Thread.current
|
47
|
-
end
|
48
|
-
|
49
|
-
def default_settings
|
50
|
-
@default_settings ||= { paranoia_field: 'deleted_at' }
|
51
|
-
end
|
52
|
-
|
53
|
-
def trackable_class_settings(trackable_class)
|
54
|
-
trackable_settings[trackable_class.name.to_sym] || default_settings
|
55
|
-
end
|
56
|
-
|
57
|
-
def reset!
|
58
|
-
Mongoid::History.modifier_class_name = 'User'
|
59
|
-
Mongoid::History.trackable_settings = {}
|
60
|
-
Mongoid::History.current_user_method ||= :current_user
|
61
|
-
|
62
|
-
Mongoid.models.each do |model|
|
63
|
-
next unless model.included_modules.include? Mongoid::History::Trackable
|
64
|
-
|
65
|
-
model.singleton_class.class_eval do
|
66
|
-
# Inverse of class_attribute
|
67
|
-
%i[mongoid_history_options
|
68
|
-
mongoid_history_options=
|
69
|
-
mongoid_history_options?].each { |m| remove_possible_method(m) }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
Mongoid::History.reset!
|
1
|
+
require 'easy_diff'
|
2
|
+
require 'mongoid/compatibility'
|
3
|
+
require 'mongoid/history/attributes/base'
|
4
|
+
require 'mongoid/history/attributes/create'
|
5
|
+
require 'mongoid/history/attributes/update'
|
6
|
+
require 'mongoid/history/attributes/destroy'
|
7
|
+
require 'mongoid/history/options'
|
8
|
+
require 'mongoid/history/version'
|
9
|
+
require 'mongoid/history/tracker'
|
10
|
+
require 'mongoid/history/trackable'
|
11
|
+
|
12
|
+
module Mongoid
|
13
|
+
module History
|
14
|
+
GLOBAL_TRACK_HISTORY_FLAG = 'mongoid_history_trackable_enabled'.freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :tracker_class_name
|
18
|
+
attr_accessor :trackable_settings
|
19
|
+
attr_accessor :modifier_class_name
|
20
|
+
attr_accessor :current_user_method
|
21
|
+
|
22
|
+
def disable
|
23
|
+
original_flag = store[GLOBAL_TRACK_HISTORY_FLAG]
|
24
|
+
store[GLOBAL_TRACK_HISTORY_FLAG] = false
|
25
|
+
yield if block_given?
|
26
|
+
ensure
|
27
|
+
store[GLOBAL_TRACK_HISTORY_FLAG] = original_flag if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
def enable
|
31
|
+
original_flag = store[GLOBAL_TRACK_HISTORY_FLAG]
|
32
|
+
store[GLOBAL_TRACK_HISTORY_FLAG] = true
|
33
|
+
yield if block_given?
|
34
|
+
ensure
|
35
|
+
store[GLOBAL_TRACK_HISTORY_FLAG] = original_flag if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
alias disable! disable
|
39
|
+
alias enable! enable
|
40
|
+
|
41
|
+
def enabled?
|
42
|
+
store[GLOBAL_TRACK_HISTORY_FLAG] != false
|
43
|
+
end
|
44
|
+
|
45
|
+
def store
|
46
|
+
defined?(RequestStore) ? RequestStore.store : Thread.current
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_settings
|
50
|
+
@default_settings ||= { paranoia_field: 'deleted_at' }
|
51
|
+
end
|
52
|
+
|
53
|
+
def trackable_class_settings(trackable_class)
|
54
|
+
trackable_settings[trackable_class.name.to_sym] || default_settings
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset!
|
58
|
+
Mongoid::History.modifier_class_name = 'User'
|
59
|
+
Mongoid::History.trackable_settings = {}
|
60
|
+
Mongoid::History.current_user_method ||= :current_user
|
61
|
+
|
62
|
+
Mongoid.models.each do |model|
|
63
|
+
next unless model.included_modules.include? Mongoid::History::Trackable
|
64
|
+
|
65
|
+
model.singleton_class.class_eval do
|
66
|
+
# Inverse of class_attribute
|
67
|
+
%i[mongoid_history_options
|
68
|
+
mongoid_history_options=
|
69
|
+
mongoid_history_options?].each { |m| remove_possible_method(m) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Mongoid::History.reset!
|
data/lib/mongoid-history.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require 'mongoid/history'
|
1
|
+
require 'mongoid/history'
|