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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +16 -3
- data/.travis.yml +23 -8
- data/CHANGELOG.md +20 -6
- data/Dangerfile +1 -0
- data/Gemfile +22 -1
- data/LICENSE.txt +1 -1
- data/README.md +130 -18
- data/lib/mongoid/history.rb +32 -15
- data/lib/mongoid/history/attributes/base.rb +31 -0
- data/lib/mongoid/history/attributes/create.rb +52 -0
- data/lib/mongoid/history/attributes/destroy.rb +36 -0
- data/lib/mongoid/history/attributes/update.rb +43 -0
- data/lib/mongoid/history/options.rb +153 -0
- data/lib/mongoid/history/trackable.rb +170 -72
- data/lib/mongoid/history/tracker.rb +39 -23
- data/lib/mongoid/history/version.rb +1 -1
- data/mongoid-history.gemspec +0 -8
- data/spec/integration/embedded_in_polymorphic_spec.rb +8 -0
- data/spec/integration/integration_spec.rb +4 -0
- data/spec/integration/nested_embedded_documents_spec.rb +13 -6
- data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +24 -24
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/attributes/base_spec.rb +54 -0
- data/spec/unit/attributes/create_spec.rb +315 -0
- data/spec/unit/attributes/destroy_spec.rb +218 -0
- data/spec/unit/attributes/update_spec.rb +210 -0
- data/spec/unit/embedded_methods_spec.rb +69 -0
- data/spec/unit/history_spec.rb +35 -0
- data/spec/unit/my_instance_methods_spec.rb +485 -0
- data/spec/unit/options_spec.rb +294 -0
- data/spec/unit/singleton_methods_spec.rb +338 -0
- data/spec/unit/store/default_store_spec.rb +11 -0
- data/spec/unit/store/request_store_spec.rb +13 -0
- data/spec/unit/trackable_spec.rb +335 -68
- data/spec/unit/tracker_spec.rb +153 -0
- 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
|
-
|
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
|
-
|
24
|
-
options
|
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[
|
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? &&
|
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
|
-
|
50
|
+
Mongoid::History.store[track_history_flag] = false
|
63
51
|
yield
|
64
52
|
ensure
|
65
|
-
|
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 ||=
|
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 ||=
|
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 ||=
|
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 ||=
|
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
|
-
|
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? &&
|
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 ||=
|
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
|
-
|
345
|
-
|
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
|
-
#
|
425
|
+
# Whether or not the embeds_one relation should be tracked.
|
349
426
|
#
|
350
|
-
# @param [ String | Symbol ]
|
427
|
+
# @param [ String | Symbol ] relation The name of the embeds_one relation
|
351
428
|
#
|
352
|
-
# @return [ Boolean ]
|
353
|
-
def
|
354
|
-
|
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
|
-
#
|
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 [
|
362
|
-
def
|
363
|
-
|
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
|
-
|
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 ]
|
451
|
+
# @param [ String | Symbol ] relation The name of the embeds_many relation
|
369
452
|
#
|
370
|
-
# @return [
|
371
|
-
def
|
372
|
-
|
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
|
-
|
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 [
|
380
|
-
def
|
381
|
-
@
|
382
|
-
|
383
|
-
|
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
|
388
|
-
|
389
|
-
|
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
|