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,104 +1,104 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module History
|
3
|
-
module Attributes
|
4
|
-
class Update < ::Mongoid::History::Attributes::Base
|
5
|
-
# @example when both an attribute `foo` and a child's attribute `nested_bar.baz` are changed
|
6
|
-
#
|
7
|
-
# {
|
8
|
-
# 'foo' => ['foo_before_changes', 'foo_after_changes']
|
9
|
-
# 'nested_bar.baz' => ['nested_bar_baz_before_changes', 'nested_bar_baz_after_changes']
|
10
|
-
# }
|
11
|
-
# }
|
12
|
-
#
|
13
|
-
# @return [Hash<String, Array<(?,?)>>] Hash of changes
|
14
|
-
def attributes
|
15
|
-
changes_from_parent.deep_merge(changes_from_children)
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def changes_from_parent
|
21
|
-
parent_changes = {}
|
22
|
-
changes.each do |k, v|
|
23
|
-
change_value = begin
|
24
|
-
if trackable_class.tracked_embeds_one?(k)
|
25
|
-
embeds_one_changes_from_parent(k, v)
|
26
|
-
elsif trackable_class.tracked_embeds_many?(k)
|
27
|
-
embeds_many_changes_from_parent(k, v)
|
28
|
-
elsif trackable_class.tracked?(k, :update)
|
29
|
-
{ k => format_field(k, v) } unless v.all?(&:blank?)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
parent_changes.merge!(change_value) if change_value.present?
|
33
|
-
end
|
34
|
-
parent_changes
|
35
|
-
end
|
36
|
-
|
37
|
-
def changes_from_children
|
38
|
-
embeds_one_changes_from_embedded_documents
|
39
|
-
end
|
40
|
-
|
41
|
-
# Retrieve the list of changes applied directly to the nested documents
|
42
|
-
#
|
43
|
-
# @example when a child's name is changed from "todd" to "mario"
|
44
|
-
#
|
45
|
-
# child = Child.new(name: 'todd')
|
46
|
-
# Parent.create(child: child)
|
47
|
-
# child.name = "Mario"
|
48
|
-
#
|
49
|
-
# embeds_one_changes_from_embedded_documents # when called from "Parent"
|
50
|
-
# # => { "child.name"=>["todd", "mario"] }
|
51
|
-
#
|
52
|
-
# @return [Hash<String, Array<(?,?)>] changes of embeds_ones from embedded documents
|
53
|
-
def embeds_one_changes_from_embedded_documents
|
54
|
-
embedded_doc_changes = {}
|
55
|
-
trackable_class.tracked_embeds_one.each do |rel|
|
56
|
-
rel_class = trackable_class.relation_class_of(rel)
|
57
|
-
paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
|
58
|
-
paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
|
59
|
-
rel = aliased_fields.key(rel) || rel
|
60
|
-
obj = trackable.send(rel)
|
61
|
-
next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?)
|
62
|
-
|
63
|
-
obj.changes.each do |k, v|
|
64
|
-
embedded_doc_changes["#{rel}.#{k}"] = [v.first, v.last]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
embedded_doc_changes
|
68
|
-
end
|
69
|
-
|
70
|
-
# @param [String] relation
|
71
|
-
# @param [String] value
|
72
|
-
#
|
73
|
-
# @return [Hash<String, Array<(?,?)>>]
|
74
|
-
def embeds_one_changes_from_parent(relation, value)
|
75
|
-
relation = trackable_class.database_field_name(relation)
|
76
|
-
relation_class = trackable_class.relation_class_of(relation)
|
77
|
-
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
|
78
|
-
original_value = value[0][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[0])
|
79
|
-
modified_value = value[1][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[1])
|
80
|
-
return if original_value == modified_value
|
81
|
-
|
82
|
-
{ relation => [original_value, modified_value] }
|
83
|
-
end
|
84
|
-
|
85
|
-
# @param [String] relation
|
86
|
-
# @param [String] value
|
87
|
-
#
|
88
|
-
# @return [Hash<Array<(?,?)>>]
|
89
|
-
def embeds_many_changes_from_parent(relation, value)
|
90
|
-
relation = trackable_class.database_field_name(relation)
|
91
|
-
relation_class = trackable_class.relation_class_of(relation)
|
92
|
-
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
|
93
|
-
original_value = value[0].reject { |rel| rel[paranoia_field].present? }
|
94
|
-
.map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
|
95
|
-
modified_value = value[1].reject { |rel| rel[paranoia_field].present? }
|
96
|
-
.map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
|
97
|
-
return if original_value == modified_value
|
98
|
-
|
99
|
-
{ relation => [original_value, modified_value] }
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
1
|
+
module Mongoid
|
2
|
+
module History
|
3
|
+
module Attributes
|
4
|
+
class Update < ::Mongoid::History::Attributes::Base
|
5
|
+
# @example when both an attribute `foo` and a child's attribute `nested_bar.baz` are changed
|
6
|
+
#
|
7
|
+
# {
|
8
|
+
# 'foo' => ['foo_before_changes', 'foo_after_changes']
|
9
|
+
# 'nested_bar.baz' => ['nested_bar_baz_before_changes', 'nested_bar_baz_after_changes']
|
10
|
+
# }
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
# @return [Hash<String, Array<(?,?)>>] Hash of changes
|
14
|
+
def attributes
|
15
|
+
changes_from_parent.deep_merge(changes_from_children)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def changes_from_parent
|
21
|
+
parent_changes = {}
|
22
|
+
changes.each do |k, v|
|
23
|
+
change_value = begin
|
24
|
+
if trackable_class.tracked_embeds_one?(k)
|
25
|
+
embeds_one_changes_from_parent(k, v)
|
26
|
+
elsif trackable_class.tracked_embeds_many?(k)
|
27
|
+
embeds_many_changes_from_parent(k, v)
|
28
|
+
elsif trackable_class.tracked?(k, :update)
|
29
|
+
{ k => format_field(k, v) } unless v.all?(&:blank?)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
parent_changes.merge!(change_value) if change_value.present?
|
33
|
+
end
|
34
|
+
parent_changes
|
35
|
+
end
|
36
|
+
|
37
|
+
def changes_from_children
|
38
|
+
embeds_one_changes_from_embedded_documents
|
39
|
+
end
|
40
|
+
|
41
|
+
# Retrieve the list of changes applied directly to the nested documents
|
42
|
+
#
|
43
|
+
# @example when a child's name is changed from "todd" to "mario"
|
44
|
+
#
|
45
|
+
# child = Child.new(name: 'todd')
|
46
|
+
# Parent.create(child: child)
|
47
|
+
# child.name = "Mario"
|
48
|
+
#
|
49
|
+
# embeds_one_changes_from_embedded_documents # when called from "Parent"
|
50
|
+
# # => { "child.name"=>["todd", "mario"] }
|
51
|
+
#
|
52
|
+
# @return [Hash<String, Array<(?,?)>] changes of embeds_ones from embedded documents
|
53
|
+
def embeds_one_changes_from_embedded_documents
|
54
|
+
embedded_doc_changes = {}
|
55
|
+
trackable_class.tracked_embeds_one.each do |rel|
|
56
|
+
rel_class = trackable_class.relation_class_of(rel)
|
57
|
+
paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
|
58
|
+
paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
|
59
|
+
rel = aliased_fields.key(rel) || rel
|
60
|
+
obj = trackable.send(rel)
|
61
|
+
next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?)
|
62
|
+
|
63
|
+
obj.changes.each do |k, v|
|
64
|
+
embedded_doc_changes["#{rel}.#{k}"] = [v.first, v.last]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
embedded_doc_changes
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param [String] relation
|
71
|
+
# @param [String] value
|
72
|
+
#
|
73
|
+
# @return [Hash<String, Array<(?,?)>>]
|
74
|
+
def embeds_one_changes_from_parent(relation, value)
|
75
|
+
relation = trackable_class.database_field_name(relation)
|
76
|
+
relation_class = trackable_class.relation_class_of(relation)
|
77
|
+
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
|
78
|
+
original_value = value[0][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[0])
|
79
|
+
modified_value = value[1][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[1])
|
80
|
+
return if original_value == modified_value
|
81
|
+
|
82
|
+
{ relation => [original_value, modified_value] }
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param [String] relation
|
86
|
+
# @param [String] value
|
87
|
+
#
|
88
|
+
# @return [Hash<Array<(?,?)>>]
|
89
|
+
def embeds_many_changes_from_parent(relation, value)
|
90
|
+
relation = trackable_class.database_field_name(relation)
|
91
|
+
relation_class = trackable_class.relation_class_of(relation)
|
92
|
+
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
|
93
|
+
original_value = value[0].reject { |rel| rel[paranoia_field].present? }
|
94
|
+
.map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
|
95
|
+
modified_value = value[1].reject { |rel| rel[paranoia_field].present? }
|
96
|
+
.map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
|
97
|
+
return if original_value == modified_value
|
98
|
+
|
99
|
+
{ relation => [original_value, modified_value] }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -1,177 +1,177 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module History
|
3
|
-
class Options
|
4
|
-
attr_reader :trackable, :options
|
5
|
-
|
6
|
-
def initialize(trackable, opts = {})
|
7
|
-
@trackable = trackable
|
8
|
-
@options = default_options.merge(opts)
|
9
|
-
end
|
10
|
-
|
11
|
-
def scope
|
12
|
-
trackable.collection_name.to_s.singularize.to_sym
|
13
|
-
end
|
14
|
-
|
15
|
-
def prepared
|
16
|
-
return @prepared if @prepared
|
17
|
-
@prepared = options.dup
|
18
|
-
prepare_skipped_fields
|
19
|
-
prepare_formatted_fields
|
20
|
-
parse_tracked_fields_and_relations
|
21
|
-
@prepared
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def default_options
|
27
|
-
{ on: :all,
|
28
|
-
except: %i[created_at updated_at],
|
29
|
-
tracker_class_name: nil,
|
30
|
-
modifier_field: :modifier,
|
31
|
-
version_field: :version,
|
32
|
-
changes_method: :changes,
|
33
|
-
scope: scope,
|
34
|
-
track_create: true,
|
35
|
-
track_update: true,
|
36
|
-
track_destroy: true,
|
37
|
-
format: nil }
|
38
|
-
end
|
39
|
-
|
40
|
-
# Sets the :except attributes and relations in `options` to be an [ Array <String> ]
|
41
|
-
# The attribute names and relations are stored by their `database_field_name`s
|
42
|
-
# Removes the `nil` and duplicate entries from skipped attributes/relations list
|
43
|
-
def prepare_skipped_fields
|
44
|
-
# normalize :except fields to an array of database field strings
|
45
|
-
@prepared[:except] = Array(@prepared[:except])
|
46
|
-
@prepared[:except] = @prepared[:except].map { |field| trackable.database_field_name(field) }.compact.uniq
|
47
|
-
end
|
48
|
-
|
49
|
-
def prepare_formatted_fields
|
50
|
-
formats = {}
|
51
|
-
|
52
|
-
if @prepared[:format].class == Hash
|
53
|
-
@prepared[:format].each do |field, format|
|
54
|
-
next if field.nil?
|
55
|
-
|
56
|
-
field = trackable.database_field_name(field)
|
57
|
-
|
58
|
-
if format.class == Hash && trackable.embeds_many?(field)
|
59
|
-
relation_class = trackable.relation_class_of(field)
|
60
|
-
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
|
61
|
-
elsif format.class == Hash && trackable.embeds_one?(field)
|
62
|
-
relation_class = trackable.relation_class_of(field)
|
63
|
-
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
|
64
|
-
else
|
65
|
-
formats[field] = format
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
@prepared[:format] = formats
|
71
|
-
end
|
72
|
-
|
73
|
-
def parse_tracked_fields_and_relations
|
74
|
-
# case `options[:on]`
|
75
|
-
# when `posts: [:id, :title]`, then it will convert it to `[[:posts, [:id, :title]]]`
|
76
|
-
# when `:foo`, then `[:foo]`
|
77
|
-
# when `[:foo, { posts: [:id, :title] }]`, then return as is
|
78
|
-
@prepared[:on] = Array(@prepared[:on])
|
79
|
-
|
80
|
-
@prepared[:on] = @prepared[:on].map { |opt| opt == :all ? :fields : opt }
|
81
|
-
|
82
|
-
if @prepared[:on].include?(:fields)
|
83
|
-
@prepared[:on] = @prepared[:on].reject { |opt| opt == :fields }
|
84
|
-
@prepared[:on] = @prepared[:on] | trackable.fields.keys.map(&:to_sym) - reserved_fields.map(&:to_sym)
|
85
|
-
end
|
86
|
-
|
87
|
-
if @prepared[:on].include?(:embedded_relations)
|
88
|
-
@prepared[:on] = @prepared[:on].reject { |opt| opt == :embedded_relations }
|
89
|
-
@prepared[:on] = @prepared[:on] | trackable.embedded_relations.keys
|
90
|
-
end
|
91
|
-
|
92
|
-
@prepared[:fields] = []
|
93
|
-
@prepared[:dynamic] = []
|
94
|
-
@prepared[:relations] = { embeds_one: {}, embeds_many: {} }
|
95
|
-
|
96
|
-
@prepared[:on].each do |option|
|
97
|
-
if option.is_a?(Hash)
|
98
|
-
option.each { |k, v| split_and_categorize(k => v) }
|
99
|
-
else
|
100
|
-
split_and_categorize(option)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def split_and_categorize(field_and_options)
|
106
|
-
field = get_database_field_name(field_and_options)
|
107
|
-
field_options = get_field_options(field_and_options)
|
108
|
-
categorize_tracked_option(field, field_options)
|
109
|
-
end
|
110
|
-
|
111
|
-
# Returns the database_field_name key for tracked option
|
112
|
-
#
|
113
|
-
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
|
114
|
-
#
|
115
|
-
# @return [ String ] the database field name for tracked option
|
116
|
-
def get_database_field_name(option)
|
117
|
-
key = if option.is_a?(Hash)
|
118
|
-
option.keys.first
|
119
|
-
elsif option.is_a?(Array)
|
120
|
-
option.first
|
121
|
-
end
|
122
|
-
trackable.database_field_name(key || option)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Returns the tracked attributes for embedded relations, otherwise `nil`
|
126
|
-
#
|
127
|
-
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
|
128
|
-
#
|
129
|
-
# @return [ nil | Array <String | Symbol> ] the list of tracked fields for embedded relation
|
130
|
-
def get_field_options(option)
|
131
|
-
if option.is_a?(Hash)
|
132
|
-
option.values.first
|
133
|
-
elsif option.is_a?(Array)
|
134
|
-
option.last
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# Tracks the passed option under:
|
139
|
-
# `fields`
|
140
|
-
# `dynamic`
|
141
|
-
# `relations -> embeds_one` or
|
142
|
-
# `relations -> embeds_many`
|
143
|
-
#
|
144
|
-
# @param [ String ] field The database field name of field or relation to track
|
145
|
-
# @param [ nil | Array <String | Symbol> ] field_options The tracked fields for embedded relations
|
146
|
-
def categorize_tracked_option(field, field_options = nil)
|
147
|
-
return if @prepared[:except].include?(field)
|
148
|
-
return if reserved_fields.include?(field)
|
149
|
-
|
150
|
-
field_options = Array(field_options)
|
151
|
-
|
152
|
-
if trackable.embeds_one?(field)
|
153
|
-
track_relation(field, :embeds_one, field_options)
|
154
|
-
elsif trackable.embeds_many?(field)
|
155
|
-
track_relation(field, :embeds_many, field_options)
|
156
|
-
elsif trackable.fields.keys.include?(field)
|
157
|
-
@prepared[:fields] << field
|
158
|
-
else
|
159
|
-
@prepared[:dynamic] << field
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def track_relation(field, kind, field_options)
|
164
|
-
relation_class = trackable.relation_class_of(field)
|
165
|
-
@prepared[:relations][kind][field] = if field_options.blank?
|
166
|
-
relation_class.fields.keys
|
167
|
-
else
|
168
|
-
%w[_id] | field_options.map { |opt| relation_class.database_field_name(opt) }
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def reserved_fields
|
173
|
-
@reserved_fields ||= ['_id', '_type', @prepared[:version_field].to_s, "#{@prepared[:modifier_field]}_id"]
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
1
|
+
module Mongoid
|
2
|
+
module History
|
3
|
+
class Options
|
4
|
+
attr_reader :trackable, :options
|
5
|
+
|
6
|
+
def initialize(trackable, opts = {})
|
7
|
+
@trackable = trackable
|
8
|
+
@options = default_options.merge(opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def scope
|
12
|
+
trackable.collection_name.to_s.singularize.to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepared
|
16
|
+
return @prepared if @prepared
|
17
|
+
@prepared = options.dup
|
18
|
+
prepare_skipped_fields
|
19
|
+
prepare_formatted_fields
|
20
|
+
parse_tracked_fields_and_relations
|
21
|
+
@prepared
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def default_options
|
27
|
+
{ on: :all,
|
28
|
+
except: %i[created_at updated_at],
|
29
|
+
tracker_class_name: nil,
|
30
|
+
modifier_field: :modifier,
|
31
|
+
version_field: :version,
|
32
|
+
changes_method: :changes,
|
33
|
+
scope: scope,
|
34
|
+
track_create: true,
|
35
|
+
track_update: true,
|
36
|
+
track_destroy: true,
|
37
|
+
format: nil }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets the :except attributes and relations in `options` to be an [ Array <String> ]
|
41
|
+
# The attribute names and relations are stored by their `database_field_name`s
|
42
|
+
# Removes the `nil` and duplicate entries from skipped attributes/relations list
|
43
|
+
def prepare_skipped_fields
|
44
|
+
# normalize :except fields to an array of database field strings
|
45
|
+
@prepared[:except] = Array(@prepared[:except])
|
46
|
+
@prepared[:except] = @prepared[:except].map { |field| trackable.database_field_name(field) }.compact.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def prepare_formatted_fields
|
50
|
+
formats = {}
|
51
|
+
|
52
|
+
if @prepared[:format].class == Hash
|
53
|
+
@prepared[:format].each do |field, format|
|
54
|
+
next if field.nil?
|
55
|
+
|
56
|
+
field = trackable.database_field_name(field)
|
57
|
+
|
58
|
+
if format.class == Hash && trackable.embeds_many?(field)
|
59
|
+
relation_class = trackable.relation_class_of(field)
|
60
|
+
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
|
61
|
+
elsif format.class == Hash && trackable.embeds_one?(field)
|
62
|
+
relation_class = trackable.relation_class_of(field)
|
63
|
+
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
|
64
|
+
else
|
65
|
+
formats[field] = format
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@prepared[:format] = formats
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_tracked_fields_and_relations
|
74
|
+
# case `options[:on]`
|
75
|
+
# when `posts: [:id, :title]`, then it will convert it to `[[:posts, [:id, :title]]]`
|
76
|
+
# when `:foo`, then `[:foo]`
|
77
|
+
# when `[:foo, { posts: [:id, :title] }]`, then return as is
|
78
|
+
@prepared[:on] = Array(@prepared[:on])
|
79
|
+
|
80
|
+
@prepared[:on] = @prepared[:on].map { |opt| opt == :all ? :fields : opt }
|
81
|
+
|
82
|
+
if @prepared[:on].include?(:fields)
|
83
|
+
@prepared[:on] = @prepared[:on].reject { |opt| opt == :fields }
|
84
|
+
@prepared[:on] = @prepared[:on] | trackable.fields.keys.map(&:to_sym) - reserved_fields.map(&:to_sym)
|
85
|
+
end
|
86
|
+
|
87
|
+
if @prepared[:on].include?(:embedded_relations)
|
88
|
+
@prepared[:on] = @prepared[:on].reject { |opt| opt == :embedded_relations }
|
89
|
+
@prepared[:on] = @prepared[:on] | trackable.embedded_relations.keys
|
90
|
+
end
|
91
|
+
|
92
|
+
@prepared[:fields] = []
|
93
|
+
@prepared[:dynamic] = []
|
94
|
+
@prepared[:relations] = { embeds_one: {}, embeds_many: {} }
|
95
|
+
|
96
|
+
@prepared[:on].each do |option|
|
97
|
+
if option.is_a?(Hash)
|
98
|
+
option.each { |k, v| split_and_categorize(k => v) }
|
99
|
+
else
|
100
|
+
split_and_categorize(option)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def split_and_categorize(field_and_options)
|
106
|
+
field = get_database_field_name(field_and_options)
|
107
|
+
field_options = get_field_options(field_and_options)
|
108
|
+
categorize_tracked_option(field, field_options)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the database_field_name key for tracked option
|
112
|
+
#
|
113
|
+
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
|
114
|
+
#
|
115
|
+
# @return [ String ] the database field name for tracked option
|
116
|
+
def get_database_field_name(option)
|
117
|
+
key = if option.is_a?(Hash)
|
118
|
+
option.keys.first
|
119
|
+
elsif option.is_a?(Array)
|
120
|
+
option.first
|
121
|
+
end
|
122
|
+
trackable.database_field_name(key || option)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the tracked attributes for embedded relations, otherwise `nil`
|
126
|
+
#
|
127
|
+
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
|
128
|
+
#
|
129
|
+
# @return [ nil | Array <String | Symbol> ] the list of tracked fields for embedded relation
|
130
|
+
def get_field_options(option)
|
131
|
+
if option.is_a?(Hash)
|
132
|
+
option.values.first
|
133
|
+
elsif option.is_a?(Array)
|
134
|
+
option.last
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Tracks the passed option under:
|
139
|
+
# `fields`
|
140
|
+
# `dynamic`
|
141
|
+
# `relations -> embeds_one` or
|
142
|
+
# `relations -> embeds_many`
|
143
|
+
#
|
144
|
+
# @param [ String ] field The database field name of field or relation to track
|
145
|
+
# @param [ nil | Array <String | Symbol> ] field_options The tracked fields for embedded relations
|
146
|
+
def categorize_tracked_option(field, field_options = nil)
|
147
|
+
return if @prepared[:except].include?(field)
|
148
|
+
return if reserved_fields.include?(field)
|
149
|
+
|
150
|
+
field_options = Array(field_options)
|
151
|
+
|
152
|
+
if trackable.embeds_one?(field)
|
153
|
+
track_relation(field, :embeds_one, field_options)
|
154
|
+
elsif trackable.embeds_many?(field)
|
155
|
+
track_relation(field, :embeds_many, field_options)
|
156
|
+
elsif trackable.fields.keys.include?(field)
|
157
|
+
@prepared[:fields] << field
|
158
|
+
else
|
159
|
+
@prepared[:dynamic] << field
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def track_relation(field, kind, field_options)
|
164
|
+
relation_class = trackable.relation_class_of(field)
|
165
|
+
@prepared[:relations][kind][field] = if field_options.blank?
|
166
|
+
relation_class.fields.keys
|
167
|
+
else
|
168
|
+
%w[_id] | field_options.map { |opt| relation_class.database_field_name(opt) }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def reserved_fields
|
173
|
+
@reserved_fields ||= ['_id', '_type', @prepared[:version_field].to_s, "#{@prepared[:modifier_field]}_id"]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|