mongoid-history 0.8.3 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|