mongoid-history 0.5.0 → 0.6.0

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.
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