mongoid-history 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +16 -3
  3. data/.travis.yml +23 -8
  4. data/CHANGELOG.md +20 -6
  5. data/Dangerfile +1 -0
  6. data/Gemfile +22 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +130 -18
  9. data/lib/mongoid/history.rb +32 -15
  10. data/lib/mongoid/history/attributes/base.rb +31 -0
  11. data/lib/mongoid/history/attributes/create.rb +52 -0
  12. data/lib/mongoid/history/attributes/destroy.rb +36 -0
  13. data/lib/mongoid/history/attributes/update.rb +43 -0
  14. data/lib/mongoid/history/options.rb +153 -0
  15. data/lib/mongoid/history/trackable.rb +170 -72
  16. data/lib/mongoid/history/tracker.rb +39 -23
  17. data/lib/mongoid/history/version.rb +1 -1
  18. data/mongoid-history.gemspec +0 -8
  19. data/spec/integration/embedded_in_polymorphic_spec.rb +8 -0
  20. data/spec/integration/integration_spec.rb +4 -0
  21. data/spec/integration/nested_embedded_documents_spec.rb +13 -6
  22. data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +24 -24
  23. data/spec/spec_helper.rb +6 -0
  24. data/spec/unit/attributes/base_spec.rb +54 -0
  25. data/spec/unit/attributes/create_spec.rb +315 -0
  26. data/spec/unit/attributes/destroy_spec.rb +218 -0
  27. data/spec/unit/attributes/update_spec.rb +210 -0
  28. data/spec/unit/embedded_methods_spec.rb +69 -0
  29. data/spec/unit/history_spec.rb +35 -0
  30. data/spec/unit/my_instance_methods_spec.rb +485 -0
  31. data/spec/unit/options_spec.rb +294 -0
  32. data/spec/unit/singleton_methods_spec.rb +338 -0
  33. data/spec/unit/store/default_store_spec.rb +11 -0
  34. data/spec/unit/store/request_store_spec.rb +13 -0
  35. data/spec/unit/trackable_spec.rb +335 -68
  36. data/spec/unit/tracker_spec.rb +153 -0
  37. metadata +31 -102
@@ -0,0 +1,31 @@
1
+ module Mongoid
2
+ module History
3
+ module Attributes
4
+ class Base
5
+ attr_reader :trackable
6
+
7
+ def initialize(trackable)
8
+ @trackable = trackable
9
+ end
10
+
11
+ private
12
+
13
+ def trackable_class
14
+ @trackable_class ||= trackable.class
15
+ end
16
+
17
+ def aliased_fields
18
+ @aliased_fields ||= trackable_class.aliased_fields
19
+ end
20
+
21
+ def changes_method
22
+ trackable_class.history_trackable_options[:changes_method]
23
+ end
24
+
25
+ def changes
26
+ trackable.send(changes_method)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ module Mongoid
2
+ module History
3
+ module Attributes
4
+ class Create < ::Mongoid::History::Attributes::Base
5
+ def attributes
6
+ @attributes = {}
7
+ trackable.attributes.each do |k, v|
8
+ next unless trackable_class.tracked_field?(k, :create)
9
+ modified = if changes[k]
10
+ changes[k].class == Array ? changes[k].last : changes[k]
11
+ else
12
+ v
13
+ end
14
+ @attributes[k] = [nil, modified]
15
+ end
16
+ insert_embeds_one_changes
17
+ insert_embeds_many_changes
18
+ @attributes
19
+ end
20
+
21
+ private
22
+
23
+ def insert_embeds_one_changes
24
+ trackable_class.tracked_embeds_one.each do |rel|
25
+ rel_class = trackable_class.embeds_one_class(rel)
26
+ paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
27
+ paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
28
+ permitted_attrs = trackable_class.tracked_embeds_one_attributes(rel)
29
+ rel = aliased_fields.key(rel) || rel
30
+ obj = trackable.send(rel)
31
+ next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?)
32
+ @attributes[rel] = [nil, obj.attributes.slice(*permitted_attrs)]
33
+ end
34
+ end
35
+
36
+ def insert_embeds_many_changes
37
+ trackable_class.tracked_embeds_many.each do |rel|
38
+ rel_class = trackable_class.embeds_many_class(rel)
39
+ paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
40
+ paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
41
+ permitted_attrs = trackable_class.tracked_embeds_many_attributes(rel)
42
+ rel = aliased_fields.key(rel) || rel
43
+ @attributes[rel] = [nil,
44
+ trackable.send(rel)
45
+ .reject { |obj| obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present? }
46
+ .map { |obj| obj.attributes.slice(*permitted_attrs) }]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ module Mongoid
2
+ module History
3
+ module Attributes
4
+ class Destroy < ::Mongoid::History::Attributes::Base
5
+ def attributes
6
+ @attributes = {}
7
+ trackable.attributes.each { |k, v| @attributes[k] = [v, nil] if trackable_class.tracked_field?(k, :destroy) }
8
+ insert_embeds_one_changes
9
+ insert_embeds_many_changes
10
+ @attributes
11
+ end
12
+
13
+ private
14
+
15
+ def insert_embeds_one_changes
16
+ trackable_class.tracked_embeds_one
17
+ .map { |rel| aliased_fields.key(rel) || rel }
18
+ .each do |rel|
19
+ permitted_attrs = trackable_class.tracked_embeds_one_attributes(rel)
20
+ obj = trackable.send(rel)
21
+ @attributes[rel] = [obj.attributes.slice(*permitted_attrs), nil] if obj
22
+ end
23
+ end
24
+
25
+ def insert_embeds_many_changes
26
+ trackable_class.tracked_embeds_many
27
+ .map { |rel| aliased_fields.key(rel) || rel }
28
+ .each do |rel|
29
+ permitted_attrs = trackable_class.tracked_embeds_many_attributes(rel)
30
+ @attributes[rel] = [trackable.send(rel).map { |obj| obj.attributes.slice(*permitted_attrs) }, nil]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ module Mongoid
2
+ module History
3
+ module Attributes
4
+ class Update < ::Mongoid::History::Attributes::Base
5
+ def attributes
6
+ @attributes = {}
7
+ changes.each do |k, v|
8
+ if trackable_class.tracked_embeds_one?(k)
9
+ insert_embeds_one_changes(k, v)
10
+ elsif trackable_class.tracked_embeds_many?(k)
11
+ insert_embeds_many_changes(k, v)
12
+ elsif trackable_class.tracked?(k, :update)
13
+ @attributes[k] = v
14
+ end
15
+ end
16
+ @attributes
17
+ end
18
+
19
+ private
20
+
21
+ def insert_embeds_one_changes(relation, value)
22
+ relation = trackable_class.database_field_name(relation)
23
+ permitted_attrs = trackable_class.tracked_embeds_one_attributes(relation)
24
+ relation_class = trackable_class.embeds_one_class(relation)
25
+ paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
26
+ @attributes[relation] = []
27
+ @attributes[relation][0] = value[0][paranoia_field].present? ? {} : value[0].slice(*permitted_attrs)
28
+ @attributes[relation][1] = value[1][paranoia_field].present? ? {} : value[1].slice(*permitted_attrs)
29
+ end
30
+
31
+ def insert_embeds_many_changes(relation, value)
32
+ relation = trackable_class.database_field_name(relation)
33
+ permitted_attrs = trackable_class.tracked_embeds_many_attributes(relation)
34
+ relation_class = trackable_class.embeds_many_class(relation)
35
+ paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
36
+ @attributes[relation] = []
37
+ @attributes[relation][0] = value[0].reject { |rel| rel[paranoia_field].present? }.map { |v_attrs| v_attrs.slice(*permitted_attrs) }
38
+ @attributes[relation][1] = value[1].reject { |rel| rel[paranoia_field].present? }.map { |v_attrs| v_attrs.slice(*permitted_attrs) }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,153 @@
1
+ module Mongoid
2
+ module History
3
+ class Options
4
+ attr_reader :trackable, :options
5
+
6
+ def initialize(trackable)
7
+ @trackable = trackable
8
+ end
9
+
10
+ def scope
11
+ trackable.collection_name.to_s.singularize.to_sym
12
+ end
13
+
14
+ def parse(options = {})
15
+ @options = default_options.merge(options)
16
+ prepare_skipped_fields
17
+ parse_tracked_fields_and_relations
18
+ @options
19
+ end
20
+
21
+ private
22
+
23
+ def default_options
24
+ @default_options ||=
25
+ { on: :all,
26
+ except: [:created_at, :updated_at],
27
+ tracker_class_name: nil,
28
+ modifier_field: :modifier,
29
+ version_field: :version,
30
+ changes_method: :changes,
31
+ scope: scope,
32
+ track_create: false,
33
+ track_update: true,
34
+ track_destroy: false }
35
+ end
36
+
37
+ # Sets the :except attributes and relations in `options` to be an [ Array <String> ]
38
+ # The attribute names and relations are stored by their `database_field_name`s
39
+ # Removes the `nil` and duplicate entries from skipped attributes/relations list
40
+ def prepare_skipped_fields
41
+ # normalize :except fields to an array of database field strings
42
+ @options[:except] = Array(options[:except])
43
+ @options[:except] = options[:except].map { |field| trackable.database_field_name(field) }.compact.uniq
44
+ end
45
+
46
+ def parse_tracked_fields_and_relations
47
+ # case `options[:on]`
48
+ # when `posts: [:id, :title]`, then it will convert it to `[[:posts, [:id, :title]]]`
49
+ # when `:foo`, then `[:foo]`
50
+ # when `[:foo, { posts: [:id, :title] }]`, then return as is
51
+ @options[:on] = Array(options[:on])
52
+
53
+ # # :all is just an alias to :fields for now, to support existing users of `mongoid-history`
54
+ # # In future, :all will track all the fields and relations of trackable class
55
+ if options[:on].include?(:all)
56
+ warn "[DEPRECATION] Use :fields instead of :all to track all fields in class #{trackable}.\n\
57
+ Going forward, :all will track all the fields and relations for the class"
58
+ end
59
+
60
+ @options[:on] = options[:on].map { |opt| (opt == :all) ? :fields : opt }
61
+
62
+ if options[:on].include?(:fields)
63
+ @options[:on] = options[:on].reject { |opt| opt == :fields }
64
+ @options[:on] = options[:on] | trackable.fields.keys.map(&:to_sym) - reserved_fields.map(&:to_sym)
65
+ end
66
+
67
+ @options[:fields] = []
68
+ @options[:dynamic] = []
69
+ @options[:relations] = { embeds_one: {}, embeds_many: {} }
70
+
71
+ options[:on].each do |option|
72
+ field = get_database_field_name(option)
73
+ field_options = get_field_options(option)
74
+ categorize_tracked_option(field, field_options)
75
+ end
76
+ end
77
+
78
+ # Returns the database_field_name key for tracked option
79
+ #
80
+ # @param [ String | Symbol | Array | Hash ] option The field or relation name to track
81
+ #
82
+ # @return [ String ] the database field name for tracked option
83
+ def get_database_field_name(option)
84
+ key = if option.is_a?(Hash)
85
+ option.keys.first
86
+ elsif option.is_a?(Array)
87
+ option.first
88
+ end
89
+ trackable.database_field_name(key || option)
90
+ end
91
+
92
+ # Returns the tracked attributes for embedded relations, otherwise `nil`
93
+ #
94
+ # @param [ String | Symbol | Array | Hash ] option The field or relation name to track
95
+ #
96
+ # @return [ nil | Array <String | Symbol> ] the list of tracked fields for embedded relation
97
+ def get_field_options(option)
98
+ if option.is_a?(Hash)
99
+ option.values.first
100
+ elsif option.is_a?(Array)
101
+ option.last
102
+ end
103
+ end
104
+
105
+ # Tracks the passed option under:
106
+ # `fields`
107
+ # `dynamic`
108
+ # `relations -> embeds_one` or
109
+ # `relations -> embeds_many`
110
+ #
111
+ # @param [ String ] field The database field name of field or relation to track
112
+ # @param [ nil | Array <String | Symbol> ] field_options The tracked fields for embedded relations
113
+ def categorize_tracked_option(field, field_options = nil)
114
+ return if options[:except].include?(field)
115
+ return if reserved_fields.include?(field)
116
+
117
+ field_options = Array(field_options)
118
+
119
+ if trackable.embeds_one?(field)
120
+ track_embeds_one(field, field_options)
121
+ elsif trackable.embeds_many?(field)
122
+ track_embeds_many(field, field_options)
123
+ elsif trackable.fields.keys.include?(field)
124
+ @options[:fields] << field
125
+ else
126
+ @options[:dynamic] << field
127
+ end
128
+ end
129
+
130
+ def track_embeds_one(field, field_options)
131
+ relation_class = trackable.embeds_one_class(field)
132
+ @options[:relations][:embeds_one][field] = if field_options.blank?
133
+ relation_class.fields.keys
134
+ else
135
+ %w(_id) | field_options.map { |opt| relation_class.database_field_name(opt) }
136
+ end
137
+ end
138
+
139
+ def track_embeds_many(field, field_options)
140
+ relation_class = trackable.embeds_many_class(field)
141
+ @options[:relations][:embeds_many][field] = if field_options.blank?
142
+ relation_class.fields.keys
143
+ else
144
+ %w(_id) | field_options.map { |opt| relation_class.database_field_name(opt) }
145
+ end
146
+ end
147
+
148
+ def reserved_fields
149
+ @reserved_fields ||= ['_id', '_type', options[:version_field].to_s, "#{options[:modifier_field]}_id"]
150
+ end
151
+ end
152
+ end
153
+ end
@@ -5,30 +5,10 @@ module Mongoid
5
5
 
6
6
  module ClassMethods
7
7
  def track_history(options = {})
8
- scope_name = collection_name.to_s.singularize.to_sym
9
- default_options = {
10
- on: :all,
11
- except: [:created_at, :updated_at],
12
- modifier_field: :modifier,
13
- version_field: :version,
14
- changes_method: :changes,
15
- scope: scope_name,
16
- track_create: false,
17
- track_update: true,
18
- track_destroy: false
19
- }
20
-
21
- options = default_options.merge(options)
8
+ extend EmbeddedMethods
22
9
 
23
- # normalize :except fields to an array of database field strings
24
- options[:except] = [options[:except]] unless options[:except].is_a? Array
25
- options[:except] = options[:except].map { |field| database_field_name(field) }.compact.uniq
26
-
27
- # normalize :on fields to either :all or an array of database field strings
28
- if options[:on] != :all
29
- options[:on] = [options[:on]] unless options[:on].is_a? Array
30
- options[:on] = options[:on].map { |field| database_field_name(field) }.compact.uniq
31
- end
10
+ options_parser = Mongoid::History::Options.new(self)
11
+ options = options_parser.parse(options)
32
12
 
33
13
  field options[:version_field].to_sym, type: Integer
34
14
 
@@ -47,11 +27,19 @@ module Mongoid
47
27
  before_destroy :track_destroy if options[:track_destroy]
48
28
 
49
29
  Mongoid::History.trackable_class_options ||= {}
50
- Mongoid::History.trackable_class_options[scope_name] = options
30
+ Mongoid::History.trackable_class_options[options_parser.scope] = options
31
+ end
32
+
33
+ def history_settings(options = {})
34
+ options = Mongoid::History.default_settings.merge(options.symbolize_keys)
35
+ options = options.slice(*Mongoid::History.default_settings.keys)
36
+ options[:paranoia_field] = aliased_fields[options[:paranoia_field].to_s] || options[:paranoia_field].to_s
37
+ Mongoid::History.trackable_settings ||= {}
38
+ Mongoid::History.trackable_settings[name.to_sym] = options
51
39
  end
52
40
 
53
41
  def track_history?
54
- Mongoid::History.enabled? && Thread.current[track_history_flag] != false
42
+ Mongoid::History.enabled? && Mongoid::History.store[track_history_flag] != false
55
43
  end
56
44
 
57
45
  def dynamic_enabled?
@@ -59,20 +47,25 @@ module Mongoid
59
47
  end
60
48
 
61
49
  def disable_tracking(&_block)
62
- Thread.current[track_history_flag] = false
50
+ Mongoid::History.store[track_history_flag] = false
63
51
  yield
64
52
  ensure
65
- Thread.current[track_history_flag] = true
53
+ Mongoid::History.store[track_history_flag] = true
66
54
  end
67
55
 
68
56
  def track_history_flag
69
57
  "mongoid_history_#{name.underscore}_trackable_enabled".to_sym
70
58
  end
59
+
60
+ def tracker_class
61
+ klass = history_trackable_options[:tracker_class_name] || Mongoid::History.tracker_class_name
62
+ klass.is_a?(Class) ? klass : klass.to_s.camelize.constantize
63
+ end
71
64
  end
72
65
 
73
66
  module MyInstanceMethods
74
67
  def history_tracks
75
- @history_tracks ||= Mongoid::History.tracker_class.where(
68
+ @history_tracks ||= self.class.tracker_class.where(
76
69
  scope: related_scope,
77
70
  association_chain: association_hash
78
71
  ).asc(:version)
@@ -212,21 +205,15 @@ module Mongoid
212
205
  end
213
206
 
214
207
  def modified_attributes_for_update
215
- @modified_attributes_for_update ||= send(history_trackable_options[:changes_method]).select { |k, _| self.class.tracked_field?(k, :update) }
208
+ @modified_attributes_for_update ||= Mongoid::History::Attributes::Update.new(self).attributes
216
209
  end
217
210
 
218
211
  def modified_attributes_for_create
219
- @modified_attributes_for_create ||= attributes.inject({}) do |h, (k, v)|
220
- h[k] = [nil, v]
221
- h
222
- end.select { |k, _| self.class.tracked_field?(k, :create) }
212
+ @modified_attributes_for_create ||= Mongoid::History::Attributes::Create.new(self).attributes
223
213
  end
224
214
 
225
215
  def modified_attributes_for_destroy
226
- @modified_attributes_for_destroy ||= attributes.inject({}) do |h, (k, v)|
227
- h[k] = [v, nil]
228
- h
229
- end.select { |k, _| self.class.tracked_field?(k, :destroy) }
216
+ @modified_attributes_for_destroy ||= Mongoid::History::Attributes::Destroy.new(self).attributes
230
217
  end
231
218
 
232
219
  def history_tracker_attributes(action)
@@ -286,13 +273,99 @@ module Mongoid
286
273
  if track_history_for_action?(action)
287
274
  current_version = (send(history_trackable_options[:version_field]) || 0) + 1
288
275
  send("#{history_trackable_options[:version_field]}=", current_version)
289
- Mongoid::History.tracker_class.create!(history_tracker_attributes(action.to_sym).merge(version: current_version, action: action.to_s, trackable: self))
276
+ self.class.tracker_class.create!(history_tracker_attributes(action.to_sym).merge(version: current_version, action: action.to_s, trackable: self))
290
277
  end
291
278
  clear_trackable_memoization
292
279
  end
293
280
  end
294
281
 
282
+ module EmbeddedMethods
283
+ # Indicates whether there is an Embedded::One relation for the given embedded field.
284
+ #
285
+ # @param [ String | Symbol ] embed The name of the embedded field
286
+ #
287
+ # @return [ Boolean ] true if there is an Embedded::One relation for the given embedded field
288
+ def embeds_one?(embed)
289
+ relation_of(embed) == Mongoid::Relations::Embedded::One
290
+ end
291
+
292
+ # Return the class for embeds_one relation
293
+ #
294
+ # @param [ String ] field The database field name for embedded relation
295
+ #
296
+ # @return [ nil | Constant ]
297
+ def embeds_one_class(field)
298
+ @embeds_one_class ||= {}
299
+ return @embeds_one_class[field] if @embeds_one_class.key?(field)
300
+ field_alias = aliased_fields.key(field)
301
+ relation = relations
302
+ .select { |_, v| v.relation == Mongoid::Relations::Embedded::One }
303
+ .detect { |rel_k, _| rel_k == field_alias }
304
+ @embeds_one_class[field] = relation && relation.last.class_name.constantize
305
+ end
306
+
307
+ # Indicates whether there is an Embedded::Many relation for the given embedded field.
308
+ #
309
+ # @param [ String | Symbol ] embed The name of the embedded field
310
+ #
311
+ # @return [ Boolean ] true if there is an Embedded::Many relation for the given embedded field
312
+ def embeds_many?(embed)
313
+ relation_of(embed) == Mongoid::Relations::Embedded::Many
314
+ end
315
+
316
+ # Return the class for embeds_many relation
317
+ #
318
+ # @param [ String ] field The database field name for embedded relation
319
+ #
320
+ # @return [ nil | Constant ]
321
+ def embeds_many_class(field)
322
+ @embeds_many_class ||= {}
323
+ return @embeds_many_class[field] if @embeds_many_class.key?(field)
324
+ field_alias = aliased_fields.key(field)
325
+ relation = relations
326
+ .select { |_, v| v.relation == Mongoid::Relations::Embedded::Many }
327
+ .detect { |rel_k, _| rel_k == field_alias }
328
+ @embeds_many_class[field] = relation && relation.last.class_name.constantize
329
+ end
330
+
331
+ # Retrieves the database representation of an embedded field name, in case the :store_as option is used.
332
+ #
333
+ # @param [ String | Symbol ] embed The name or alias of the embedded field
334
+ #
335
+ # @return [ String ] the database name of the embedded field
336
+ def embedded_alias(embed)
337
+ embedded_aliases[embed]
338
+ end
339
+
340
+ protected
341
+
342
+ # Retrieves the memoized hash of embedded aliases and their associated database representations.
343
+ #
344
+ # @return [ Hash < String, String > ] hash of embedded aliases (keys) to database representations (values)
345
+ def embedded_aliases
346
+ @embedded_aliases ||= relations.inject(HashWithIndifferentAccess.new) do |h, (k, v)|
347
+ h[v[:store_as] || k] = k
348
+ h
349
+ end
350
+ end
351
+
352
+ def relation_of(embed)
353
+ meta = reflect_on_association(embedded_alias(embed))
354
+ meta ? meta.relation : nil
355
+ end
356
+ end
357
+
295
358
  module SingletonMethods
359
+ # Whether or not the field or embedded relation should be tracked.
360
+ #
361
+ # @param [ String | Symbol ] field_or_relation The name or alias of the field OR the name of embedded relation
362
+ # @param [ String | Symbol ] action The optional action name (:create, :update, or :destroy)
363
+ #
364
+ # @return [ Boolean ] whether or not the field or embedded relation is tracked for the given action
365
+ def tracked?(field_or_relation, action = :update)
366
+ tracked_field?(field_or_relation, action) || tracked_relation?(field_or_relation)
367
+ end
368
+
296
369
  # Whether or not the field should be tracked.
297
370
  #
298
371
  # @param [ String | Symbol ] field The name or alias of the field
@@ -309,7 +382,9 @@ module Mongoid
309
382
  #
310
383
  # @return [ Boolean ] whether or not the field is dynamic
311
384
  def dynamic_field?(field)
312
- dynamic_enabled? && !fields.keys.include?(database_field_name(field))
385
+ dynamic_enabled? &&
386
+ !fields.keys.include?(database_field_name(field)) &&
387
+ !embedded_relations.map { |_, v| v.key }.include?(database_field_name(field))
313
388
  end
314
389
 
315
390
  # Retrieves the list of tracked fields for a given action.
@@ -328,10 +403,7 @@ module Mongoid
328
403
  #
329
404
  # @return [ Array < String > ] the base list of tracked database field names
330
405
  def tracked_fields
331
- @tracked_fields ||= begin
332
- h = history_trackable_options
333
- (h[:on] == :all ? fields.keys : h[:on]) - h[:except] - reserved_tracked_fields
334
- end
406
+ @tracked_fields ||= history_trackable_options[:fields] + history_trackable_options[:dynamic]
335
407
  end
336
408
 
337
409
  # Retrieves the memoized list of reserved tracked fields, which are only included for certain actions.
@@ -341,52 +413,78 @@ module Mongoid
341
413
  @reserved_tracked_fields ||= ['_id', history_trackable_options[:version_field].to_s, "#{history_trackable_options[:modifier_field]}_id"]
342
414
  end
343
415
 
344
- def history_trackable_options
345
- @history_trackable_options ||= Mongoid::History.trackable_class_options[collection_name.to_s.singularize.to_sym]
416
+ # Whether or not the relation should be tracked.
417
+ #
418
+ # @param [ String | Symbol ] relation The name of the relation
419
+ #
420
+ # @return [ Boolean ] whether or not the relation is tracked
421
+ def tracked_relation?(relation)
422
+ tracked_embeds_one?(relation) || tracked_embeds_many?(relation)
346
423
  end
347
424
 
348
- # Indicates whether there is an Embedded::One relation for the given embedded field.
425
+ # Whether or not the embeds_one relation should be tracked.
349
426
  #
350
- # @param [ String | Symbol ] embed The name of the embedded field
427
+ # @param [ String | Symbol ] relation The name of the embeds_one relation
351
428
  #
352
- # @return [ Boolean ] true if there is an Embedded::One relation for the given embedded field
353
- def embeds_one?(embed)
354
- relation_of(embed) == Mongoid::Relations::Embedded::One
429
+ # @return [ Boolean ] whether or not the embeds_one relation is tracked
430
+ def tracked_embeds_one?(relation)
431
+ tracked_embeds_one.include?(database_field_name(relation))
355
432
  end
356
433
 
357
- # Indicates whether there is an Embedded::Many relation for the given embedded field.
358
- #
359
- # @param [ String | Symbol ] embed The name of the embedded field
434
+ # Retrieves the memoized list of tracked embeds_one relations
360
435
  #
361
- # @return [ Boolean ] true if there is an Embedded::Many relation for the given embedded field
362
- def embeds_many?(embed)
363
- relation_of(embed) == Mongoid::Relations::Embedded::Many
436
+ # @return [ Array < String > ] the list of tracked embeds_one relations
437
+ def tracked_embeds_one
438
+ @tracked_embeds_one ||= begin
439
+ reflect_on_all_associations(:embeds_one)
440
+ .map(&:key)
441
+ .select { |rel| history_trackable_options[:relations][:embeds_one].include? rel }
442
+ end
364
443
  end
365
444
 
366
- # Retrieves the database representation of an embedded field name, in case the :store_as option is used.
445
+ def tracked_embeds_one_attributes(relation)
446
+ history_trackable_options[:relations][:embeds_one][database_field_name(relation)]
447
+ end
448
+
449
+ # Whether or not the embeds_many relation should be tracked.
367
450
  #
368
- # @param [ String | Symbol ] embed The name or alias of the embedded field
451
+ # @param [ String | Symbol ] relation The name of the embeds_many relation
369
452
  #
370
- # @return [ String ] the database name of the embedded field
371
- def embedded_alias(embed)
372
- embedded_aliases[embed]
453
+ # @return [ Boolean ] whether or not the embeds_many relation is tracked
454
+ def tracked_embeds_many?(relation)
455
+ tracked_embeds_many.include?(database_field_name(relation))
373
456
  end
374
457
 
375
- protected
376
-
377
- # Retrieves the memoized hash of embedded aliases and their associated database representations.
458
+ # Retrieves the memoized list of tracked embeds_many relations
378
459
  #
379
- # @return [ Hash < String, String > ] hash of embedded aliases (keys) to database representations (values)
380
- def embedded_aliases
381
- @embedded_aliases ||= relations.inject(HashWithIndifferentAccess.new) do |h, (k, v)|
382
- h[v[:store_as] || k] = k
383
- h
460
+ # @return [ Array < String > ] the list of tracked embeds_many relations
461
+ def tracked_embeds_many
462
+ @tracked_embeds_many ||= begin
463
+ reflect_on_all_associations(:embeds_many)
464
+ .map(&:key)
465
+ .select { |rel| history_trackable_options[:relations][:embeds_many].include? rel }
384
466
  end
385
467
  end
386
468
 
387
- def relation_of(embed)
388
- meta = reflect_on_association(embedded_alias(embed))
389
- meta ? meta.relation : nil
469
+ def tracked_embeds_many_attributes(relation)
470
+ history_trackable_options[:relations][:embeds_many][database_field_name(relation)]
471
+ end
472
+
473
+ def trackable_scope
474
+ collection_name.to_s.singularize.to_sym
475
+ end
476
+
477
+ def history_trackable_options
478
+ @history_trackable_options ||= Mongoid::History.trackable_class_options[trackable_scope]
479
+ end
480
+
481
+ def clear_trackable_memoization
482
+ @reserved_tracked_fields = nil
483
+ @history_trackable_options = nil
484
+ @trackable_settings = nil
485
+ @tracked_fields = nil
486
+ @tracked_embeds_one = nil
487
+ @tracked_embeds_many = nil
390
488
  end
391
489
  end
392
490
  end