mongoid 9.0.8 → 9.0.10
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/lib/mongoid/association/embedded/embeds_many/proxy.rb +3 -29
- data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +0 -6
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +40 -0
- data/lib/mongoid/association/referenced/has_many/proxy.rb +17 -1
- data/lib/mongoid/attributes.rb +21 -2
- data/lib/mongoid/clients/factory.rb +4 -0
- data/lib/mongoid/contextual/mongo.rb +8 -89
- data/lib/mongoid/criteria.rb +39 -5
- data/lib/mongoid/pluckable.rb +132 -0
- data/lib/mongoid/traversable.rb +0 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +394 -0
- data/spec/mongoid/attributes_spec.rb +30 -1
- data/spec/mongoid/clients/factory_spec.rb +32 -0
- data/spec/mongoid/criteria_spec.rb +196 -0
- data/spec/shared/CANDIDATE.md +28 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +32 -3
- data/spec/shared/shlib/server.sh +1 -1
- data/spec/support/models/company.rb +2 -0
- data/spec/support/models/passport.rb +1 -0
- data/spec/support/models/product.rb +2 -0
- data/spec/support/models/seo.rb +2 -0
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fefd49681b6f78b222f201fbaea83af9f1599464034e52a73117c0ae94e201f2
|
|
4
|
+
data.tar.gz: d86f966109921d2a0f1a4ce0b78d81e0d722123c1d420f0f695385ca43687626
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09799846ca2a44c7dc4d7aff76fba0f9fdcbe38c5bfb001dccd08eb467800f8fac675cecfa06be94eb76ca35f6f1b57b64557ed171e9c0040bcbe0a701d7827f'
|
|
7
|
+
data.tar.gz: bc0a741e2e26a84e84238f1461a26a29ae13584d9bd050dffc1ccd04f70e4e6a7fa63b20e7dcda8137ce3c37d383a5ae916b0d72ff950d4f330e66e0635ea89c
|
|
@@ -14,6 +14,7 @@ module Mongoid
|
|
|
14
14
|
# the array of child documents.
|
|
15
15
|
class Proxy < Association::Many
|
|
16
16
|
include Batchable
|
|
17
|
+
extend Forwardable
|
|
17
18
|
|
|
18
19
|
# Class-level methods for the Proxy class.
|
|
19
20
|
module ClassMethods
|
|
@@ -54,6 +55,8 @@ module Mongoid
|
|
|
54
55
|
|
|
55
56
|
extend ClassMethods
|
|
56
57
|
|
|
58
|
+
def_delegators :criteria, :find, :pluck
|
|
59
|
+
|
|
57
60
|
# Instantiate a new embeds_many association.
|
|
58
61
|
#
|
|
59
62
|
# @example Create the new association.
|
|
@@ -312,35 +315,6 @@ module Mongoid
|
|
|
312
315
|
end
|
|
313
316
|
end
|
|
314
317
|
|
|
315
|
-
# Finds a document in this association through several different
|
|
316
|
-
# methods.
|
|
317
|
-
#
|
|
318
|
-
# This method delegates to +Mongoid::Criteria#find+. If this method is
|
|
319
|
-
# not given a block, it returns one or many documents for the provided
|
|
320
|
-
# _id values.
|
|
321
|
-
#
|
|
322
|
-
# If this method is given a block, it returns the first document
|
|
323
|
-
# of those found by the current Criteria object for which the block
|
|
324
|
-
# returns a truthy value.
|
|
325
|
-
#
|
|
326
|
-
# @example Find a document by its id.
|
|
327
|
-
# person.addresses.find(BSON::ObjectId.new)
|
|
328
|
-
#
|
|
329
|
-
# @example Find documents for multiple ids.
|
|
330
|
-
# person.addresses.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
|
|
331
|
-
#
|
|
332
|
-
# @example Finds the first matching document using a block.
|
|
333
|
-
# person.addresses.find { |addr| addr.state == 'CA' }
|
|
334
|
-
#
|
|
335
|
-
# @param [ Object... ] *args Various arguments.
|
|
336
|
-
# @param &block Optional block to pass.
|
|
337
|
-
# @yield [ Object ] Yields each enumerable element to the block.
|
|
338
|
-
#
|
|
339
|
-
# @return [ Document | Array<Document> | nil ] A document or matching documents.
|
|
340
|
-
def find(...)
|
|
341
|
-
criteria.find(...)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
318
|
# Get all the documents in the association that are loaded into memory.
|
|
345
319
|
#
|
|
346
320
|
# @example Get the in memory documents.
|
|
@@ -53,8 +53,6 @@ module Mongoid
|
|
|
53
53
|
# @param [ Document... ] *args Any number of documents.
|
|
54
54
|
#
|
|
55
55
|
# @return [ Array<Document> ] The loaded docs.
|
|
56
|
-
#
|
|
57
|
-
# rubocop:disable Metrics/AbcSize
|
|
58
56
|
def <<(*args)
|
|
59
57
|
docs = args.flatten
|
|
60
58
|
return concat(docs) if docs.size > 1
|
|
@@ -89,7 +87,6 @@ module Mongoid
|
|
|
89
87
|
end
|
|
90
88
|
unsynced(_base, foreign_key) and self
|
|
91
89
|
end
|
|
92
|
-
# rubocop:enable Metrics/AbcSize
|
|
93
90
|
|
|
94
91
|
alias push <<
|
|
95
92
|
|
|
@@ -360,8 +357,6 @@ module Mongoid
|
|
|
360
357
|
# in bulk
|
|
361
358
|
# @param [ Array ] inserts the list of Hashes of attributes that will
|
|
362
359
|
# be inserted (corresponding to the ``docs`` list)
|
|
363
|
-
#
|
|
364
|
-
# rubocop:disable Metrics/AbcSize
|
|
365
360
|
def append_document(doc, ids, docs, inserts)
|
|
366
361
|
return unless doc
|
|
367
362
|
|
|
@@ -379,7 +374,6 @@ module Mongoid
|
|
|
379
374
|
unsynced(_base, foreign_key)
|
|
380
375
|
end
|
|
381
376
|
end
|
|
382
|
-
# rubocop:enable Metrics/AbcSize
|
|
383
377
|
end
|
|
384
378
|
end
|
|
385
379
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rubocop:todo all
|
|
3
3
|
|
|
4
|
+
require 'mongoid/pluckable'
|
|
5
|
+
|
|
4
6
|
module Mongoid
|
|
5
7
|
module Association
|
|
6
8
|
module Referenced
|
|
@@ -12,6 +14,7 @@ module Mongoid
|
|
|
12
14
|
class Enumerable
|
|
13
15
|
extend Forwardable
|
|
14
16
|
include ::Enumerable
|
|
17
|
+
include Pluckable
|
|
15
18
|
|
|
16
19
|
# The three main instance variables are collections of documents.
|
|
17
20
|
#
|
|
@@ -374,6 +377,43 @@ module Mongoid
|
|
|
374
377
|
@_added, @_loaded, @_unloaded, @executed = data
|
|
375
378
|
end
|
|
376
379
|
|
|
380
|
+
# Plucks the given field names from the documents in the target.
|
|
381
|
+
# If the collection has been loaded, it plucks from the loaded
|
|
382
|
+
# documents; otherwise, it plucks from the unloaded criteria.
|
|
383
|
+
# Regardless, it also plucks from any added documents.
|
|
384
|
+
#
|
|
385
|
+
# @param [ Symbol... ] *fields The field names to pluck.
|
|
386
|
+
#
|
|
387
|
+
# @return [ Array | Array<Array> ] The array of field values. If
|
|
388
|
+
# multiple fields are given, an array of arrays is returned.
|
|
389
|
+
def pluck(*keys)
|
|
390
|
+
[].tap do |results|
|
|
391
|
+
if _loaded? || _added.any?
|
|
392
|
+
klass = @_association.klass
|
|
393
|
+
prepared = prepare_pluck(keys, document_class: klass)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
if _loaded?
|
|
397
|
+
docs = _loaded.values.map { |v| BSON::Document.new(v.attributes) }
|
|
398
|
+
results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
|
|
399
|
+
elsif _unloaded
|
|
400
|
+
criteria = if _added.any?
|
|
401
|
+
ids_to_exclude = _added.keys
|
|
402
|
+
_unloaded.not(:_id.in => ids_to_exclude)
|
|
403
|
+
else
|
|
404
|
+
_unloaded
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
results.concat criteria.pluck(*keys)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
if _added.any?
|
|
411
|
+
docs = _added.values.map { |v| BSON::Document.new(v.attributes) }
|
|
412
|
+
results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
377
417
|
# Reset the enumerable back to its persisted state.
|
|
378
418
|
#
|
|
379
419
|
# @example Reset the enumerable.
|
|
@@ -33,7 +33,7 @@ module Mongoid
|
|
|
33
33
|
|
|
34
34
|
extend ClassMethods
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
def_delegators :criteria, :count
|
|
37
37
|
def_delegators :_target, :first, :in_memory, :last, :reset, :uniq
|
|
38
38
|
|
|
39
39
|
# Instantiate a new references_many association. Will set the foreign key
|
|
@@ -281,6 +281,22 @@ module Mongoid
|
|
|
281
281
|
|
|
282
282
|
alias nullify_all nullify
|
|
283
283
|
|
|
284
|
+
# Plucks the given field names from the documents in the
|
|
285
|
+
# association. It is safe to use whether the association is
|
|
286
|
+
# loaded or not, and whether there are unsaved documents in the
|
|
287
|
+
# association or not.
|
|
288
|
+
#
|
|
289
|
+
# @example Pluck the titles of all posts.
|
|
290
|
+
# person.posts.pluck(:title)
|
|
291
|
+
#
|
|
292
|
+
# @param [ Symbol... ] *fields The field names to pluck.
|
|
293
|
+
#
|
|
294
|
+
# @return [ Array | Array<Array> ] The array of field values. If
|
|
295
|
+
# multiple fields are given, an array of arrays is returned.
|
|
296
|
+
def pluck(*fields)
|
|
297
|
+
_target.pluck(*fields)
|
|
298
|
+
end
|
|
299
|
+
|
|
284
300
|
# Clear the association. Will delete the documents from the db if they are
|
|
285
301
|
# already persisted.
|
|
286
302
|
#
|
data/lib/mongoid/attributes.rb
CHANGED
|
@@ -104,7 +104,8 @@ module Mongoid
|
|
|
104
104
|
# @api private
|
|
105
105
|
def process_raw_attribute(name, raw, field)
|
|
106
106
|
value = field ? field.demongoize(raw) : raw
|
|
107
|
-
|
|
107
|
+
is_relation = relations.key?(name)
|
|
108
|
+
attribute_will_change!(name) if value.resizable? && !is_relation
|
|
108
109
|
value
|
|
109
110
|
end
|
|
110
111
|
|
|
@@ -175,7 +176,7 @@ module Mongoid
|
|
|
175
176
|
localized = fields[field_name].try(:localized?)
|
|
176
177
|
attributes_before_type_cast[name.to_s] = value
|
|
177
178
|
typed_value = typed_value_for(field_name, value)
|
|
178
|
-
unless
|
|
179
|
+
unless attribute_will_not_change?(field_name, typed_value) || attribute_changed?(field_name)
|
|
179
180
|
attribute_will_change!(field_name)
|
|
180
181
|
end
|
|
181
182
|
if localized
|
|
@@ -366,5 +367,23 @@ module Mongoid
|
|
|
366
367
|
end
|
|
367
368
|
value.present?
|
|
368
369
|
end
|
|
370
|
+
|
|
371
|
+
# If `value` is a `BSON::Decimal128`, convert it to a `BigDecimal` for
|
|
372
|
+
# comparison purposes. This is necessary because `BSON::Decimal128` does
|
|
373
|
+
# not implement `#==` in a way that is compatible with `BigDecimal`.
|
|
374
|
+
def normalize_value(value)
|
|
375
|
+
value.is_a?(BSON::Decimal128) ? BigDecimal(value.to_s) : value
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Determine if the attribute will not change, by comparing the current
|
|
379
|
+
# value with the new value. The values are normalized to account for
|
|
380
|
+
# types that do not implement `#==` in a way that is compatible with
|
|
381
|
+
# each other, such as `BSON::Decimal128` and `BigDecimal`.
|
|
382
|
+
def attribute_will_not_change?(field_name, typed_value)
|
|
383
|
+
normalized_attribute = normalize_value(attributes[field_name])
|
|
384
|
+
normalized_typed_value = normalize_value(typed_value)
|
|
385
|
+
|
|
386
|
+
normalized_attribute == normalized_typed_value
|
|
387
|
+
end
|
|
369
388
|
end
|
|
370
389
|
end
|
|
@@ -131,6 +131,10 @@ module Mongoid
|
|
|
131
131
|
[MONGOID_WRAPPING_LIBRARY] + options[:wrapping_libraries]
|
|
132
132
|
else
|
|
133
133
|
[MONGOID_WRAPPING_LIBRARY]
|
|
134
|
+
end.tap do |wrap|
|
|
135
|
+
if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
136
|
+
wrap << { name: 'Rails', version: ::Rails.version }
|
|
137
|
+
end
|
|
134
138
|
end
|
|
135
139
|
options[:wrapping_libraries] = wrap_lib
|
|
136
140
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# rubocop:todo all
|
|
3
3
|
|
|
4
4
|
require 'mongoid/atomic_update_preparer'
|
|
5
|
+
require 'mongoid/pluckable'
|
|
5
6
|
require "mongoid/contextual/mongo/documents_loader"
|
|
6
7
|
require "mongoid/contextual/atomic"
|
|
7
8
|
require "mongoid/contextual/aggregable/mongo"
|
|
@@ -22,6 +23,7 @@ module Mongoid
|
|
|
22
23
|
include Atomic
|
|
23
24
|
include Association::EagerLoadable
|
|
24
25
|
include Queryable
|
|
26
|
+
include Pluckable
|
|
25
27
|
|
|
26
28
|
# Options constant.
|
|
27
29
|
OPTIONS = [ :hint,
|
|
@@ -331,23 +333,12 @@ module Mongoid
|
|
|
331
333
|
# in the array will be a single value. Otherwise, each
|
|
332
334
|
# result in the array will be an array of values.
|
|
333
335
|
def pluck(*fields)
|
|
334
|
-
# Multiple fields can map to the same field name. For example,
|
|
335
|
-
# a field and its _translations field map to the same
|
|
336
|
-
# because of this, we need to
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
normalized_field_names.push(db_fn)
|
|
341
|
-
hash[klass.cleanse_localized_field_names(f)] = true
|
|
342
|
-
hash
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
view.projection(normalized_select).reduce([]) do |plucked, doc|
|
|
346
|
-
values = normalized_field_names.map do |n|
|
|
347
|
-
extract_value(doc, n)
|
|
348
|
-
end
|
|
349
|
-
plucked << (values.size == 1 ? values.first : values)
|
|
350
|
-
end
|
|
336
|
+
# Multiple fields can map to the same field name. For example,
|
|
337
|
+
# plucking a field and its _translations field map to the same
|
|
338
|
+
# field in the database. because of this, we need to prepare the
|
|
339
|
+
# projection specifically.
|
|
340
|
+
prep = prepare_pluck(fields, prepare_projection: true)
|
|
341
|
+
pluck_from_documents(view.projection(prep[:projection]), prep[:field_names])
|
|
351
342
|
end
|
|
352
343
|
|
|
353
344
|
# Pick the single field values from the database.
|
|
@@ -893,78 +884,6 @@ module Mongoid
|
|
|
893
884
|
collection.write_concern.nil? || collection.write_concern.acknowledged?
|
|
894
885
|
end
|
|
895
886
|
|
|
896
|
-
# Fetch the element from the given hash and demongoize it using the
|
|
897
|
-
# given field. If the obj is an array, map over it and call this method
|
|
898
|
-
# on all of its elements.
|
|
899
|
-
#
|
|
900
|
-
# @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
|
|
901
|
-
# @param [ String ] meth The key to fetch from the hash.
|
|
902
|
-
# @param [ Field ] field The field to use for demongoization.
|
|
903
|
-
#
|
|
904
|
-
# @return [ Object ] The demongoized value.
|
|
905
|
-
#
|
|
906
|
-
# @api private
|
|
907
|
-
def fetch_and_demongoize(obj, meth, field)
|
|
908
|
-
if obj.is_a?(Array)
|
|
909
|
-
obj.map { |doc| fetch_and_demongoize(doc, meth, field) }
|
|
910
|
-
else
|
|
911
|
-
res = obj.try(:fetch, meth, nil)
|
|
912
|
-
field ? field.demongoize(res) : res.class.demongoize(res)
|
|
913
|
-
end
|
|
914
|
-
end
|
|
915
|
-
|
|
916
|
-
# Extracts the value for the given field name from the given attribute
|
|
917
|
-
# hash.
|
|
918
|
-
#
|
|
919
|
-
# @param [ Hash ] attrs The attributes hash.
|
|
920
|
-
# @param [ String ] field_name The name of the field to extract.
|
|
921
|
-
#
|
|
922
|
-
# @param [ Object ] The value for the given field name
|
|
923
|
-
def extract_value(attrs, field_name)
|
|
924
|
-
i = 1
|
|
925
|
-
num_meths = field_name.count('.') + 1
|
|
926
|
-
curr = attrs.dup
|
|
927
|
-
|
|
928
|
-
klass.traverse_association_tree(field_name) do |meth, obj, is_field|
|
|
929
|
-
field = obj if is_field
|
|
930
|
-
is_translation = false
|
|
931
|
-
# If no association or field was found, check if the meth is an
|
|
932
|
-
# _translations field.
|
|
933
|
-
if obj.nil? & tr = meth.match(/(.*)_translations\z/)&.captures&.first
|
|
934
|
-
is_translation = true
|
|
935
|
-
meth = tr
|
|
936
|
-
end
|
|
937
|
-
|
|
938
|
-
# 1. If curr is an array fetch from all elements in the array.
|
|
939
|
-
# 2. If the field is localized, and is not an _translations field
|
|
940
|
-
# (_translations fields don't show up in the fields hash).
|
|
941
|
-
# - If this is the end of the methods, return the translation for
|
|
942
|
-
# the current locale.
|
|
943
|
-
# - Otherwise, return the whole translations hash so the next method
|
|
944
|
-
# can select the language it wants.
|
|
945
|
-
# 3. If the meth is an _translations field, do not demongoize the
|
|
946
|
-
# value so the full hash is returned.
|
|
947
|
-
# 4. Otherwise, fetch and demongoize the value for the key meth.
|
|
948
|
-
curr = if curr.is_a? Array
|
|
949
|
-
res = fetch_and_demongoize(curr, meth, field)
|
|
950
|
-
res.empty? ? nil : res
|
|
951
|
-
elsif !is_translation && field&.localized?
|
|
952
|
-
if i < num_meths
|
|
953
|
-
curr.try(:fetch, meth, nil)
|
|
954
|
-
else
|
|
955
|
-
fetch_and_demongoize(curr, meth, field)
|
|
956
|
-
end
|
|
957
|
-
elsif is_translation
|
|
958
|
-
curr.try(:fetch, meth, nil)
|
|
959
|
-
else
|
|
960
|
-
fetch_and_demongoize(curr, meth, field)
|
|
961
|
-
end
|
|
962
|
-
|
|
963
|
-
i += 1
|
|
964
|
-
end
|
|
965
|
-
curr
|
|
966
|
-
end
|
|
967
|
-
|
|
968
887
|
# Recursively demongoize the given value. This method recursively traverses
|
|
969
888
|
# the class tree to find the correct field to use to demongoize the value.
|
|
970
889
|
#
|
data/lib/mongoid/criteria.rb
CHANGED
|
@@ -41,24 +41,57 @@ module Mongoid
|
|
|
41
41
|
include Clients::Sessions
|
|
42
42
|
include Options
|
|
43
43
|
|
|
44
|
+
# Allowed methods for from_hash to prevent arbitrary method execution.
|
|
45
|
+
# Only query-building methods are allowed, not execution or modification methods.
|
|
46
|
+
ALLOWED_FROM_HASH_METHODS = %i[
|
|
47
|
+
all all_in all_of and any_in any_of asc ascending
|
|
48
|
+
batch_size between
|
|
49
|
+
collation comment cursor_type
|
|
50
|
+
desc descending
|
|
51
|
+
elem_match eq exists extras
|
|
52
|
+
geo_spatial group gt gte
|
|
53
|
+
hint
|
|
54
|
+
in includes
|
|
55
|
+
limit lt lte
|
|
56
|
+
max_distance max_scan max_time_ms merge mod
|
|
57
|
+
ne near near_sphere nin no_timeout none none_of nor not not_in
|
|
58
|
+
offset only or order order_by
|
|
59
|
+
project
|
|
60
|
+
raw read reorder
|
|
61
|
+
scoped skip slice snapshot
|
|
62
|
+
text_search type
|
|
63
|
+
unscoped unwind
|
|
64
|
+
where with_size with_type without
|
|
65
|
+
].freeze
|
|
66
|
+
|
|
44
67
|
class << self
|
|
45
68
|
# Convert the given hash to a criteria. Will iterate over each keys in the
|
|
46
|
-
# hash which must correspond to method on a criteria object. The hash
|
|
47
|
-
#
|
|
69
|
+
# hash which must correspond to an allowed method on a criteria object. The hash
|
|
70
|
+
# can include a "klass" key that specifies the model class for the criteria.
|
|
48
71
|
#
|
|
49
72
|
# @example Convert the hash to a criteria.
|
|
50
73
|
# Criteria.from_hash({ klass: Band, where: { name: "Depeche Mode" })
|
|
51
74
|
#
|
|
75
|
+
# @deprecated This method is deprecated and will
|
|
76
|
+
# be removed in a future release.
|
|
77
|
+
#
|
|
52
78
|
# @param [ Hash ] hash The hash to convert.
|
|
53
79
|
#
|
|
54
80
|
# @return [ Criteria ] The criteria.
|
|
81
|
+
#
|
|
82
|
+
# @raise [ ArgumentError ] If a method is not allowed in from_hash.
|
|
55
83
|
def from_hash(hash)
|
|
56
84
|
criteria = Criteria.new(hash.delete(:klass) || hash.delete('klass'))
|
|
57
85
|
hash.each_pair do |method, args|
|
|
58
|
-
|
|
86
|
+
method_sym = method.to_sym
|
|
87
|
+
unless ALLOWED_FROM_HASH_METHODS.include?(method_sym)
|
|
88
|
+
raise ArgumentError, "Method '#{method}' is not allowed in from_hash"
|
|
89
|
+
end
|
|
90
|
+
criteria = criteria.public_send(method_sym, args)
|
|
59
91
|
end
|
|
60
92
|
criteria
|
|
61
93
|
end
|
|
94
|
+
Mongoid.deprecate(self, :from_hash)
|
|
62
95
|
end
|
|
63
96
|
|
|
64
97
|
# Static array used to check with method missing - we only need to ever
|
|
@@ -246,7 +279,8 @@ module Mongoid
|
|
|
246
279
|
# criteria.merge(other_criteria)
|
|
247
280
|
#
|
|
248
281
|
# @example Merge the criteria with a hash. The hash must contain a klass
|
|
249
|
-
# key
|
|
282
|
+
# key that specifies the model class for the criteria and the key/value
|
|
283
|
+
# pairs correspond to method names/args.
|
|
250
284
|
#
|
|
251
285
|
# criteria.merge({
|
|
252
286
|
# klass: Band,
|
|
@@ -254,7 +288,7 @@ module Mongoid
|
|
|
254
288
|
# order_by: { name: 1 }
|
|
255
289
|
# })
|
|
256
290
|
#
|
|
257
|
-
# @param [ Criteria ] other The other criterion to merge with.
|
|
291
|
+
# @param [ Criteria | Hash ] other The other criterion to merge with.
|
|
258
292
|
#
|
|
259
293
|
# @return [ Criteria ] A cloned self.
|
|
260
294
|
def merge(other)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
# Provides shared behavior for any document with "pluck" functionality.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module Pluckable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Prepares the field names for plucking by normalizing them to their
|
|
13
|
+
# database field names. Also prepares a projection hash if requested.
|
|
14
|
+
def prepare_pluck(field_names, document_class: klass, prepare_projection: false)
|
|
15
|
+
normalized_field_names = []
|
|
16
|
+
projection = {}
|
|
17
|
+
|
|
18
|
+
field_names.each do |f|
|
|
19
|
+
db_fn = document_class.database_field_name(f)
|
|
20
|
+
normalized_field_names.push(db_fn)
|
|
21
|
+
|
|
22
|
+
next unless prepare_projection
|
|
23
|
+
|
|
24
|
+
cleaned_name = document_class.cleanse_localized_field_names(f)
|
|
25
|
+
canonical_name = document_class.database_field_name(cleaned_name)
|
|
26
|
+
projection[canonical_name] = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
{ field_names: normalized_field_names, projection: projection }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Plucks the given field names from the given documents.
|
|
33
|
+
def pluck_from_documents(documents, field_names, document_class: klass)
|
|
34
|
+
documents.reduce([]) do |plucked, doc|
|
|
35
|
+
values = field_names.map { |name| extract_value(doc, name.to_s, document_class) }
|
|
36
|
+
plucked << ((values.size == 1) ? values.first : values)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Fetch the element from the given hash and demongoize it using the
|
|
41
|
+
# given field. If the obj is an array, map over it and call this method
|
|
42
|
+
# on all of its elements.
|
|
43
|
+
#
|
|
44
|
+
# @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
|
|
45
|
+
# @param [ String ] key The key to fetch from the hash.
|
|
46
|
+
# @param [ Field ] field The field to use for demongoization.
|
|
47
|
+
#
|
|
48
|
+
# @return [ Object ] The demongoized value.
|
|
49
|
+
def fetch_and_demongoize(obj, key, field)
|
|
50
|
+
if obj.is_a?(Array)
|
|
51
|
+
obj.map { |doc| fetch_and_demongoize(doc, key, field) }
|
|
52
|
+
else
|
|
53
|
+
value = obj.try(:fetch, key, nil)
|
|
54
|
+
field ? field.demongoize(value) : value.class.demongoize(value)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Extracts the value for the given field name from the given attribute
|
|
59
|
+
# hash.
|
|
60
|
+
#
|
|
61
|
+
# @param [ Hash ] attrs The attributes hash.
|
|
62
|
+
# @param [ String ] field_name The name of the field to extract.
|
|
63
|
+
#
|
|
64
|
+
# @return [ Object ] The value for the given field name
|
|
65
|
+
def extract_value(attrs, field_name, document_class)
|
|
66
|
+
i = 1
|
|
67
|
+
num_meths = field_name.count('.') + 1
|
|
68
|
+
curr = attrs.dup
|
|
69
|
+
|
|
70
|
+
document_class.traverse_association_tree(field_name) do |meth, obj, is_field|
|
|
71
|
+
field = obj if is_field
|
|
72
|
+
|
|
73
|
+
# use the correct document class to check for localized fields on
|
|
74
|
+
# embedded documents.
|
|
75
|
+
document_class = obj.klass if obj.respond_to?(:klass)
|
|
76
|
+
|
|
77
|
+
is_translation = false
|
|
78
|
+
# If no association or field was found, check if the meth is an
|
|
79
|
+
# _translations field.
|
|
80
|
+
if obj.nil? && (tr = meth.match(/(.*)_translations\z/)&.captures&.first)
|
|
81
|
+
is_translation = true
|
|
82
|
+
meth = document_class.database_field_name(tr)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
curr = descend(i, curr, meth, field, num_meths, is_translation)
|
|
86
|
+
|
|
87
|
+
i += 1
|
|
88
|
+
end
|
|
89
|
+
curr
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Descend one level in the attribute hash.
|
|
93
|
+
#
|
|
94
|
+
# @param [ Integer ] part The current part index.
|
|
95
|
+
# @param [ Hash | Array<Hash> ] current The current level in the attribute hash.
|
|
96
|
+
# @param [ String ] method_name The method name to descend to.
|
|
97
|
+
# @param [ Field|nil ] field The field to use for demongoization.
|
|
98
|
+
# @param [ Boolean ] is_translation Whether the method is an _translations field.
|
|
99
|
+
# @param [ Integer ] part_count The total number of parts in the field name.
|
|
100
|
+
#
|
|
101
|
+
# @return [ Object ] The value at the next level.
|
|
102
|
+
#
|
|
103
|
+
# rubocop:disable Metrics/ParameterLists
|
|
104
|
+
def descend(part, current, method_name, field, part_count, is_translation)
|
|
105
|
+
# 1. If curr is an array fetch from all elements in the array.
|
|
106
|
+
# 2. If the field is localized, and is not an _translations field
|
|
107
|
+
# (_translations fields don't show up in the fields hash).
|
|
108
|
+
# - If this is the end of the methods, return the translation for
|
|
109
|
+
# the current locale.
|
|
110
|
+
# - Otherwise, return the whole translations hash so the next method
|
|
111
|
+
# can select the language it wants.
|
|
112
|
+
# 3. If the meth is an _translations field, do not demongoize the
|
|
113
|
+
# value so the full hash is returned.
|
|
114
|
+
# 4. Otherwise, fetch and demongoize the value for the key meth.
|
|
115
|
+
if current.is_a? Array
|
|
116
|
+
res = fetch_and_demongoize(current, method_name, field)
|
|
117
|
+
res.empty? ? nil : res
|
|
118
|
+
elsif !is_translation && field&.localized?
|
|
119
|
+
if part < part_count
|
|
120
|
+
current.try(:fetch, method_name, nil)
|
|
121
|
+
else
|
|
122
|
+
fetch_and_demongoize(current, method_name, field)
|
|
123
|
+
end
|
|
124
|
+
elsif is_translation
|
|
125
|
+
current.try(:fetch, method_name, nil)
|
|
126
|
+
else
|
|
127
|
+
fetch_and_demongoize(current, method_name, field)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
# rubocop:enable Metrics/ParameterLists
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/mongoid/traversable.rb
CHANGED
|
@@ -131,7 +131,6 @@ module Mongoid
|
|
|
131
131
|
# @param [ String ] value The discriminator key to set.
|
|
132
132
|
#
|
|
133
133
|
# @api private
|
|
134
|
-
# rubocop:disable Metrics/AbcSize
|
|
135
134
|
def discriminator_key=(value)
|
|
136
135
|
raise Errors::InvalidDiscriminatorKeyTarget.new(self, superclass) if hereditary?
|
|
137
136
|
|
|
@@ -159,7 +158,6 @@ module Mongoid
|
|
|
159
158
|
default_proc = -> { self.class.discriminator_value }
|
|
160
159
|
field(discriminator_key, default: default_proc, type: String)
|
|
161
160
|
end
|
|
162
|
-
# rubocop:enable Metrics/AbcSize
|
|
163
161
|
|
|
164
162
|
# Returns the discriminator key.
|
|
165
163
|
#
|
data/lib/mongoid/version.rb
CHANGED