mongoid 7.1.0 → 7.1.1
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +6 -6
- data/README.md +1 -1
- data/lib/config/locales/en.yml +4 -4
- data/lib/mongoid/association/referenced/belongs_to/eager.rb +38 -2
- data/lib/mongoid/association/referenced/eager.rb +29 -9
- data/lib/mongoid/config.rb +39 -9
- data/lib/mongoid/criteria.rb +16 -3
- data/lib/mongoid/criteria/queryable/pipeline.rb +3 -2
- data/lib/mongoid/criteria/queryable/selectable.rb +94 -7
- data/lib/mongoid/criteria/queryable/storable.rb +104 -99
- data/lib/mongoid/errors/eager_load.rb +2 -0
- data/lib/mongoid/persistable/pushable.rb +7 -1
- data/lib/mongoid/serializable.rb +9 -3
- data/lib/mongoid/version.rb +1 -1
- data/lib/rails/generators/mongoid/config/templates/mongoid.yml +32 -23
- data/spec/app/models/coding.rb +4 -0
- data/spec/app/models/coding/pull_request.rb +12 -0
- data/spec/app/models/delegating_patient.rb +16 -0
- data/spec/app/models/publication.rb +5 -0
- data/spec/app/models/publication/encyclopedia.rb +12 -0
- data/spec/app/models/publication/review.rb +14 -0
- data/spec/integration/document_spec.rb +22 -0
- data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +193 -10
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +504 -127
- data/spec/mongoid/criteria/queryable/selectable_spec.rb +52 -0
- data/spec/mongoid/criteria/queryable/storable_spec.rb +80 -2
- data/spec/mongoid/criteria_spec.rb +32 -0
- data/spec/mongoid/persistable/pushable_spec.rb +55 -1
- data/spec/mongoid/serializable_spec.rb +129 -18
- data/spec/spec_helper.rb +2 -0
- data/spec/support/expectations.rb +3 -1
- data/spec/support/helpers.rb +11 -0
- metadata +504 -490
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b90b3442fbb22055d692fca89eb33e90868e45588812f1975926dee5a85d8b10
|
4
|
+
data.tar.gz: d9c2a5eaf30d5ca34a365be11b648efc8932a7cda1b29fd8982847cf9cb528cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca1480e9cbd438a8905ef4d8f1e9ac59e66ee6436b0017f40c350de6421dd5f99a8a96c1bfa31bb0291738ad97bdad5ce18ee2ccc60a0cea2d91f1795abd5a8c
|
7
|
+
data.tar.gz: a3f7da9b676f022dc9bf48199eeb34f68fc14e2c076b539a81149401a6b1b81914f96ba21daca7a392b40a5bf5ed8ef9b76d7499033b786b530d161a1e3bb29b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1848,18 +1848,18 @@ child elements.
|
|
1848
1848
|
set a child on a relation without the proper inverse_of definitions
|
1849
1849
|
due to Mongoid not being able to determine it.
|
1850
1850
|
|
1851
|
-
class
|
1851
|
+
class Car
|
1852
1852
|
include Mongoid::Document
|
1853
|
-
embeds_one :
|
1853
|
+
embeds_one :engine, class_name: "Motor"
|
1854
1854
|
end
|
1855
1855
|
|
1856
|
-
class
|
1856
|
+
class Motor
|
1857
1857
|
include Mongoid::Document
|
1858
|
-
embedded_in :
|
1858
|
+
embedded_in :machine, class_name: "Car"
|
1859
1859
|
end
|
1860
1860
|
|
1861
|
-
|
1862
|
-
|
1861
|
+
car = Car.new
|
1862
|
+
car.engine = Motor.new # raises an InverseNotFound error.
|
1863
1863
|
|
1864
1864
|
* \#1680 Polymorphic relations now use `*_type` keys in lookup queries.
|
1865
1865
|
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ Support
|
|
31
31
|
-------
|
32
32
|
|
33
33
|
* [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoid)
|
34
|
-
* [
|
34
|
+
* [MongoDB Community Forum](https://developer.mongodb.com/community/forums/tags/c/drivers-odms-connectors/7/mongoid-odm)
|
35
35
|
* [#mongoid](http://webchat.freenode.net/?channels=mongoid) on Freenode IRC
|
36
36
|
|
37
37
|
License
|
data/lib/config/locales/en.yml
CHANGED
@@ -282,13 +282,13 @@ en:
|
|
282
282
|
you will need to explicitly tell Mongoid on the association what
|
283
283
|
the inverse is.\n\n
|
284
284
|
Example:\n
|
285
|
-
\_\_class
|
285
|
+
\_\_class Car\n
|
286
286
|
\_\_\_\_include Mongoid::Document\n
|
287
|
-
\_\_\_\_has_one :
|
287
|
+
\_\_\_\_has_one :engine, class_name: \"Motor\", inverse_of: :machine\n
|
288
288
|
\_\_end\n\n
|
289
|
-
\_\_class
|
289
|
+
\_\_class Motor\n
|
290
290
|
\_\_\_\_include Mongoid::Document\n
|
291
|
-
\_\_\_\_belongs_to :
|
291
|
+
\_\_\_\_belongs_to :machine, class_name: \"Car\", inverse_of: :engine\n
|
292
292
|
\_\_end"
|
293
293
|
invalid_set_polymorphic_relation:
|
294
294
|
message: "The %{name} attribute can't be set to an instance of
|
@@ -12,8 +12,6 @@ module Mongoid
|
|
12
12
|
private
|
13
13
|
|
14
14
|
def preload
|
15
|
-
raise Errors::EagerLoad.new(@association.name) if @association.polymorphic?
|
16
|
-
|
17
15
|
@docs.each do |d|
|
18
16
|
set_relation(d, nil)
|
19
17
|
end
|
@@ -24,6 +22,44 @@ module Mongoid
|
|
24
22
|
end
|
25
23
|
end
|
26
24
|
|
25
|
+
# Retrieves the documents referenced by the association, and
|
26
|
+
# yields each one sequentially to the provided block. If the
|
27
|
+
# association is not polymorphic, all documents are retrieved in
|
28
|
+
# a single query. If the association is polymorphic, one query is
|
29
|
+
# issued per association target class.
|
30
|
+
def each_loaded_document(&block)
|
31
|
+
if @association.polymorphic?
|
32
|
+
keys_by_type_from_docs.each do |type, keys|
|
33
|
+
each_loaded_document_of_class(Object.const_get(type), keys, &block)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a map from association target class name to foreign key
|
41
|
+
# values for the documents of that association target class,
|
42
|
+
# as referenced by this association.
|
43
|
+
def keys_by_type_from_docs
|
44
|
+
inverse_type_field = @association.inverse_type
|
45
|
+
|
46
|
+
@docs.each_with_object({}) do |doc, keys_by_type|
|
47
|
+
next unless doc.respond_to?(inverse_type_field) && doc.respond_to?(group_by_key)
|
48
|
+
inverse_type_name = doc.send(inverse_type_field)
|
49
|
+
# If a particular document does not have a value for this
|
50
|
+
# association, inverse_type_name will be nil.
|
51
|
+
next if inverse_type_name.nil?
|
52
|
+
|
53
|
+
key_value = doc.send(group_by_key)
|
54
|
+
# If a document has the *_type field set but the corresponding
|
55
|
+
# *_id field not set, the key value here will be nil.
|
56
|
+
next unless key_value
|
57
|
+
|
58
|
+
keys_by_type[inverse_type_name] ||= []
|
59
|
+
keys_by_type[inverse_type_name].push(key_value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
27
63
|
def group_by_key
|
28
64
|
@association.foreign_key
|
29
65
|
end
|
@@ -59,17 +59,29 @@ module Mongoid
|
|
59
59
|
raise NotImplementedError
|
60
60
|
end
|
61
61
|
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
62
|
+
# Retrieves the documents referenced by the association, and
|
63
|
+
# yields each one sequentially to the provided block. If the
|
64
|
+
# association is not polymorphic, all documents are retrieved in
|
65
|
+
# a single query. If the association is polymorphic, one query is
|
66
|
+
# issued per association target class.
|
66
67
|
#
|
67
68
|
# @since 4.0.0
|
68
|
-
def each_loaded_document
|
69
|
-
|
70
|
-
|
69
|
+
def each_loaded_document(&block)
|
70
|
+
each_loaded_document_of_class(@association.klass, keys_from_docs, &block)
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
+
# Retrieves the documents of the specified class, that have the
|
74
|
+
# foreign key included in the specified list of keys.
|
75
|
+
#
|
76
|
+
# When the documents are retrieved, the set of inclusions applied
|
77
|
+
# is the set of inclusions applied to the host document minus the
|
78
|
+
# association that is being eagerly loaded.
|
79
|
+
private def each_loaded_document_of_class(cls, keys)
|
80
|
+
# Note: keys should not include nil elements.
|
81
|
+
# Upstream code is responsible for eliminating nils from keys.
|
82
|
+
return cls.none if keys.empty?
|
83
|
+
|
84
|
+
criteria = cls.any_in(key => keys)
|
73
85
|
criteria.inclusions = criteria.inclusions - [@association]
|
74
86
|
criteria.each do |doc|
|
75
87
|
yield doc
|
@@ -93,6 +105,9 @@ module Mongoid
|
|
93
105
|
|
94
106
|
# Return a hash with the current documents grouped by key.
|
95
107
|
#
|
108
|
+
# Documents that do not have a value for the association being loaded
|
109
|
+
# are not returned.
|
110
|
+
#
|
96
111
|
# @example Return a hash with the current documents grouped by key.
|
97
112
|
# loader.grouped_docs
|
98
113
|
#
|
@@ -102,10 +117,15 @@ module Mongoid
|
|
102
117
|
def grouped_docs
|
103
118
|
@grouped_docs[@association.name] ||= @docs.group_by do |doc|
|
104
119
|
doc.send(group_by_key) if doc.respond_to?(group_by_key)
|
120
|
+
end.reject do |k, v|
|
121
|
+
k.nil?
|
105
122
|
end
|
106
123
|
end
|
107
124
|
|
108
|
-
# Group the documents and return the keys
|
125
|
+
# Group the documents and return the keys.
|
126
|
+
#
|
127
|
+
# This method omits nil keys (i.e. keys from documents that do not
|
128
|
+
# have a value for the association being loaded).
|
109
129
|
#
|
110
130
|
# @example
|
111
131
|
# loader.keys_from_docs
|
data/lib/mongoid/config.rb
CHANGED
@@ -18,14 +18,31 @@ module Mongoid
|
|
18
18
|
|
19
19
|
LOCK = Mutex.new
|
20
20
|
|
21
|
+
# Application name that is printed to the mongodb logs upon establishing
|
22
|
+
# a connection in server versions >= 3.4. Note that the name cannot
|
23
|
+
# exceed 128 bytes. It is also used as the database name if the
|
24
|
+
# database name is not explicitly defined.
|
25
|
+
option :app_name, default: nil
|
26
|
+
|
27
|
+
# Create indexes in background by default.
|
28
|
+
option :background_indexing, default: false
|
29
|
+
|
30
|
+
# Mark belongs_to associations as required by default, so that saving a
|
31
|
+
# model with a missing belongs_to association will trigger a validation
|
32
|
+
# error.
|
33
|
+
option :belongs_to_required_by_default, default: true
|
34
|
+
|
35
|
+
# Raise an exception when a field is redefined.
|
36
|
+
option :duplicate_fields_exception, default: false
|
37
|
+
|
38
|
+
# Include the root model name in json serialization.
|
21
39
|
option :include_root_in_json, default: false
|
40
|
+
|
41
|
+
# # Include the _type field in serialization.
|
22
42
|
option :include_type_for_serialization, default: false
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
option :duplicate_fields_exception, default: false
|
27
|
-
option :use_activesupport_time_zone, default: true
|
28
|
-
option :use_utc, default: false
|
43
|
+
|
44
|
+
# Whether to join nested persistence contexts for atomic operations
|
45
|
+
# to parent contexts by default.
|
29
46
|
option :join_contexts, default: false
|
30
47
|
|
31
48
|
# The log level.
|
@@ -38,9 +55,22 @@ module Mongoid
|
|
38
55
|
# configuration file is the log level given by this option honored.
|
39
56
|
option :log_level, default: :info
|
40
57
|
|
41
|
-
|
42
|
-
option :
|
43
|
-
|
58
|
+
# Preload all models in development, needed when models use inheritance.
|
59
|
+
option :preload_models, default: false
|
60
|
+
|
61
|
+
# Raise an error when performing a #find and the document is not found.
|
62
|
+
option :raise_not_found_error, default: true
|
63
|
+
|
64
|
+
# Raise an error when defining a scope with the same name as an
|
65
|
+
# existing method.
|
66
|
+
option :scope_overwrite_exception, default: false
|
67
|
+
|
68
|
+
# Use ActiveSupport's time zone in time operations instead of the
|
69
|
+
# Ruby default time zone.
|
70
|
+
option :use_activesupport_time_zone, default: true
|
71
|
+
|
72
|
+
# Return stored times as UTC.
|
73
|
+
option :use_utc, default: false
|
44
74
|
|
45
75
|
# Has Mongoid been configured? This is checking that at least a valid
|
46
76
|
# client config exists.
|
data/lib/mongoid/criteria.rb
CHANGED
@@ -400,9 +400,22 @@ module Mongoid
|
|
400
400
|
# @return [ Criteria ] The cloned selectable.
|
401
401
|
#
|
402
402
|
# @since 1.0.0
|
403
|
-
def where(
|
404
|
-
|
405
|
-
|
403
|
+
def where(*args)
|
404
|
+
# Historically this method required exactly one argument.
|
405
|
+
# As of https://jira.mongodb.org/browse/MONGOID-4804 it also accepts
|
406
|
+
# zero arguments.
|
407
|
+
# The underlying where implemetation that super invokes supports
|
408
|
+
# any number of arguments, but we don't presently allow mutiple
|
409
|
+
# arguments through this method. This API can be reconsidered in the
|
410
|
+
# future.
|
411
|
+
if args.length > 1
|
412
|
+
raise ArgumentError, "Criteria#where requires zero or one arguments (given #{args.length})"
|
413
|
+
end
|
414
|
+
if args.length == 1
|
415
|
+
expression = args.first
|
416
|
+
if expression.is_a?(::String) && embedded?
|
417
|
+
raise Errors::UnsupportedJavascript.new(klass, expression)
|
418
|
+
end
|
406
419
|
end
|
407
420
|
super
|
408
421
|
end
|
@@ -33,7 +33,7 @@ module Mongoid
|
|
33
33
|
# Add a group operation to the aggregation pipeline.
|
34
34
|
#
|
35
35
|
# @example Add a group operation.
|
36
|
-
# pipeline.group(:count.sum => 1, :max.max => "likes")
|
36
|
+
# pipeline.group(:_id => "foo", :count.sum => 1, :max.max => "likes")
|
37
37
|
#
|
38
38
|
# @param [ Hash ] entry The group entry.
|
39
39
|
#
|
@@ -76,7 +76,8 @@ module Mongoid
|
|
76
76
|
# pipeline.unwind(:field)
|
77
77
|
# pipeline.unwind(document)
|
78
78
|
#
|
79
|
-
# @param [ String, Symbol, Hash ] field_or_doc
|
79
|
+
# @param [ String, Symbol, Hash ] field_or_doc A field name or a
|
80
|
+
# document.
|
80
81
|
#
|
81
82
|
# @return [ Pipeline ] The pipeline.
|
82
83
|
#
|
@@ -92,8 +92,8 @@ module Mongoid
|
|
92
92
|
normalized = _mongoid_normalize_expr(new_s)
|
93
93
|
normalized.each do |k, v|
|
94
94
|
k = k.to_s
|
95
|
-
if c.selector[k]
|
96
|
-
c = c.send(:__multi__, [
|
95
|
+
if c.selector[k]
|
96
|
+
c = c.send(:__multi__, [k => v], '$and')
|
97
97
|
else
|
98
98
|
c.selector.store(k, v)
|
99
99
|
end
|
@@ -586,13 +586,34 @@ module Mongoid
|
|
586
586
|
end
|
587
587
|
key :not, :override, "$not"
|
588
588
|
|
589
|
-
#
|
589
|
+
# Creates a disjunction using $or from the existing criteria in the
|
590
|
+
# receiver and the provided arguments.
|
590
591
|
#
|
591
|
-
#
|
592
|
+
# This behavior (receiver becoming one of the disjunction operands)
|
593
|
+
# matches ActiveRecord's +or+ behavior.
|
594
|
+
#
|
595
|
+
# Use +any_of+ to add a disjunction of the arguments as an additional
|
596
|
+
# constraint to the criteria already existing in the receiver.
|
597
|
+
#
|
598
|
+
# Each argument can be a Hash, a Criteria object, an array of
|
599
|
+
# Hash or Criteria objects, or a nested array. Nested arrays will be
|
600
|
+
# flattened and can be of any depth. Passing arrays is deprecated.
|
601
|
+
#
|
602
|
+
# @example Add the $or selection where both fields must have the specified values.
|
592
603
|
# selectable.or(field: 1, field: 2)
|
593
604
|
#
|
594
|
-
# @
|
595
|
-
#
|
605
|
+
# @example Add the $or selection where either value match is sufficient.
|
606
|
+
# selectable.or({field: 1}, {field: 2})
|
607
|
+
#
|
608
|
+
# @example Same as previous example but using the deprecated array wrap.
|
609
|
+
# selectable.or([{field: 1}, {field: 2}])
|
610
|
+
#
|
611
|
+
# @example Same as previous example, also deprecated.
|
612
|
+
# selectable.or([{field: 1}], [{field: 2}])
|
613
|
+
#
|
614
|
+
# @param [ Hash | Criteria | Array<Hash | Criteria>, ... ] criteria
|
615
|
+
# Multiple key/value pair matches or Criteria objects, or arrays
|
616
|
+
# thereof. Passing arrays is deprecated.
|
596
617
|
#
|
597
618
|
# @return [ Selectable ] The new selectable.
|
598
619
|
#
|
@@ -600,7 +621,73 @@ module Mongoid
|
|
600
621
|
def or(*criteria)
|
601
622
|
_mongoid_add_top_level_operation('$or', criteria)
|
602
623
|
end
|
603
|
-
|
624
|
+
|
625
|
+
# Adds a disjunction of the arguments as an additional constraint
|
626
|
+
# to the criteria already existing in the receiver.
|
627
|
+
#
|
628
|
+
# Use +or+ to make the receiver one of the disjunction operands.
|
629
|
+
#
|
630
|
+
# Each argument can be a Hash, a Criteria object, an array of
|
631
|
+
# Hash or Criteria objects, or a nested array. Nested arrays will be
|
632
|
+
# flattened and can be of any depth. Passing arrays is deprecated.
|
633
|
+
#
|
634
|
+
# @example Add the $or selection where both fields must have the specified values.
|
635
|
+
# selectable.any_of(field: 1, field: 2)
|
636
|
+
#
|
637
|
+
# @example Add the $or selection where either value match is sufficient.
|
638
|
+
# selectable.any_of({field: 1}, {field: 2})
|
639
|
+
#
|
640
|
+
# @example Same as previous example but using the deprecated array wrap.
|
641
|
+
# selectable.any_of([{field: 1}, {field: 2}])
|
642
|
+
#
|
643
|
+
# @example Same as previous example, also deprecated.
|
644
|
+
# selectable.any_of([{field: 1}], [{field: 2}])
|
645
|
+
#
|
646
|
+
# @param [ Hash | Criteria | Array<Hash | Criteria>, ... ] criteria
|
647
|
+
# Multiple key/value pair matches or Criteria objects, or arrays
|
648
|
+
# thereof. Passing arrays is deprecated.
|
649
|
+
#
|
650
|
+
# @return [ Selectable ] The new selectable.
|
651
|
+
#
|
652
|
+
# @since 1.0.0
|
653
|
+
def any_of(*criteria)
|
654
|
+
criteria = _mongoid_flatten_arrays(criteria)
|
655
|
+
case criteria.length
|
656
|
+
when 0
|
657
|
+
clone
|
658
|
+
when 1
|
659
|
+
# When we have a single criteria, any_of behaves like and.
|
660
|
+
# Note: criteria can be a Query object, which #where method does
|
661
|
+
# not support.
|
662
|
+
self.and(*criteria)
|
663
|
+
else
|
664
|
+
# When we have multiple criteria, combine them all with $or
|
665
|
+
# and add the result to self.
|
666
|
+
exprs = criteria.map do |criterion|
|
667
|
+
if criterion.is_a?(Selectable)
|
668
|
+
_mongoid_normalize_expr(criterion.selector)
|
669
|
+
else
|
670
|
+
Hash[criterion.map do |k, v|
|
671
|
+
if k.is_a?(Symbol)
|
672
|
+
[k.to_s, v]
|
673
|
+
else
|
674
|
+
[k, v]
|
675
|
+
end
|
676
|
+
end]
|
677
|
+
end
|
678
|
+
end
|
679
|
+
# Should be able to do:
|
680
|
+
#where('$or' => exprs)
|
681
|
+
# But since that is broken do instead:
|
682
|
+
clone.tap do |query|
|
683
|
+
if query.selector['$or']
|
684
|
+
query.selector.store('$or', query.selector['$or'] + exprs)
|
685
|
+
else
|
686
|
+
query.selector.store('$or', exprs)
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
604
691
|
|
605
692
|
# Add a $size selection for array fields.
|
606
693
|
#
|
@@ -17,67 +17,49 @@ module Mongoid
|
|
17
17
|
# @api private
|
18
18
|
module Storable
|
19
19
|
|
20
|
-
# Adds
|
21
|
-
#
|
22
|
-
# This method takes the operator and the operator value expression
|
23
|
-
# separately for callers' convenience. It can be considered to
|
24
|
-
# handle storing the hash `{operator => op_expr}`.
|
25
|
-
#
|
26
|
-
# If the selector already has the specified operator in it (on the
|
27
|
-
# top level), the new condition given in op_expr is added to the
|
28
|
-
# existing conditions for the specified operator. This is
|
29
|
-
# straightforward for $and; for other logical operators, the behavior
|
30
|
-
# of this method is to add the new conditions to the existing operator.
|
31
|
-
# For example, if the selector is currently:
|
32
|
-
#
|
33
|
-
# {'foo' => 'bar', '$or' => [{'hello' => 'world'}]}
|
34
|
-
#
|
35
|
-
# ... and operator is '$or' and op_expr is `{'test' => 123'}`,
|
36
|
-
# the resulting selector will be:
|
37
|
-
#
|
38
|
-
# {'foo' => 'bar', '$or' => [{'hello' => 'world'}, {'test' => 123}]}
|
20
|
+
# Adds a field expression to the query.
|
39
21
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# assumption that the existing selector is the correct left hand side
|
44
|
-
# of the operation already.
|
22
|
+
# +field+ must be a field name, and it must be a string. The upstream
|
23
|
+
# code must have converted other field/key types to the simple string
|
24
|
+
# form by the time this method is invoked.
|
45
25
|
#
|
46
|
-
#
|
47
|
-
# there already is a top-level operator with the same name, the
|
48
|
-
# op_expr is added to the selector via a top-level $and operator,
|
49
|
-
# thus producing a selector having both operator values.
|
26
|
+
# +value+ can be of any type, it is written into the selector unchanged.
|
50
27
|
#
|
51
|
-
# This method
|
52
|
-
# currently empty and operator is $and, op_expr is written to the
|
53
|
-
# selector with $and even if the $and can in principle be elided).
|
28
|
+
# This method performs no processing on the provided field value.
|
54
29
|
#
|
55
|
-
#
|
30
|
+
# Mutates the receiver.
|
56
31
|
#
|
57
|
-
# @param [ String ]
|
58
|
-
# @param [
|
32
|
+
# @param [ String ] field The field name.
|
33
|
+
# @param [ Object ] value The field value.
|
59
34
|
#
|
60
35
|
# @return [ Storable ] self.
|
61
|
-
def
|
62
|
-
unless
|
63
|
-
raise ArgumentError, "
|
64
|
-
end
|
65
|
-
|
66
|
-
unless operator[0] == ?$
|
67
|
-
raise ArgumentError, "Operator must begin with $: #{operator}"
|
36
|
+
def add_field_expression(field, value)
|
37
|
+
unless field.is_a?(String)
|
38
|
+
raise ArgumentError, "Field must be a string: #{field}"
|
68
39
|
end
|
69
40
|
|
70
|
-
if
|
71
|
-
|
41
|
+
if field[0] == ?$
|
42
|
+
raise ArgumentError, "Field cannot be an operator (i.e. begin with $): #{field}"
|
72
43
|
end
|
73
44
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
45
|
+
if selector[field]
|
46
|
+
# We already have a restriction by the field we are trying
|
47
|
+
# to restrict, combine the restrictions.
|
48
|
+
if value.is_a?(Hash) && selector[field].is_a?(Hash) &&
|
49
|
+
value.keys.all? { |key|
|
50
|
+
key_s = key.to_s
|
51
|
+
key_s[0] == ?$ && !selector[field].key?(key_s)
|
52
|
+
}
|
53
|
+
then
|
54
|
+
# Multiple operators can be combined on the same field by
|
55
|
+
# adding them to the existing hash.
|
56
|
+
new_value = selector[field].merge(value)
|
57
|
+
selector.store(field, new_value)
|
58
|
+
else
|
59
|
+
add_operator_expression('$and', [{field => value}])
|
60
|
+
end
|
79
61
|
else
|
80
|
-
selector.store(
|
62
|
+
selector.store(field, value)
|
81
63
|
end
|
82
64
|
|
83
65
|
self
|
@@ -87,41 +69,40 @@ module Mongoid
|
|
87
69
|
#
|
88
70
|
# This method only handles logical operators ($and, $nor and $or).
|
89
71
|
# It raises ArgumentError if called with another operator. Note that
|
90
|
-
# in
|
91
|
-
# $not is not handled by this method
|
72
|
+
# in MQL, $not is a field-level operator and not a query-level one,
|
73
|
+
# and therefore $not is not handled by this method.
|
92
74
|
#
|
93
75
|
# This method takes the operator and the operator value expression
|
94
76
|
# separately for callers' convenience. It can be considered to
|
95
77
|
# handle storing the hash `{operator => op_expr}`.
|
96
78
|
#
|
97
|
-
# If the selector
|
98
|
-
# top level), the new condition given in op_expr is
|
99
|
-
# existing conditions for the specified operator.
|
100
|
-
# straightforward for $and; for other logical operators, the behavior
|
101
|
-
# of this method is to add the new conditions to the existing operator.
|
79
|
+
# If the selector consists of a single condition which is the specified
|
80
|
+
# operator (on the top level), the new condition given in op_expr is
|
81
|
+
# added to the existing conditions for the specified operator.
|
102
82
|
# For example, if the selector is currently:
|
103
83
|
#
|
104
|
-
# {'
|
84
|
+
# {'$or' => [{'hello' => 'world'}]}
|
105
85
|
#
|
106
|
-
# ... and operator is '$or' and op_expr is `{'test' => 123'}`,
|
86
|
+
# ... and operator is '$or' and op_expr is `[{'test' => 123'}]`,
|
107
87
|
# the resulting selector will be:
|
108
88
|
#
|
109
|
-
# {'
|
89
|
+
# {'$or' => [{'hello' => 'world'}, {'test' => 123}]}
|
110
90
|
#
|
111
|
-
# This
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
# of the operation already.
|
91
|
+
# This method always adds the new conditions as additional requirements;
|
92
|
+
# in other words, it does not implement the ActiveRecord or/nor behavior
|
93
|
+
# where the receiver becomes one of the operands. It is expected that
|
94
|
+
# code upstream of this method implements such behavior.
|
116
95
|
#
|
117
96
|
# This method does not simplify values (i.e. if the selector is
|
118
97
|
# currently empty and operator is $and, op_expr is written to the
|
119
98
|
# selector with $and even if the $and can in principle be elided).
|
99
|
+
# Such simplification is also expected to have already been performed
|
100
|
+
# by the upstream code.
|
120
101
|
#
|
121
102
|
# This method mutates the receiver.
|
122
103
|
#
|
123
104
|
# @param [ String ] operator The operator to add.
|
124
|
-
# @param [ Hash ] op_expr Operator value to add.
|
105
|
+
# @param [ Array<Hash> ] op_expr Operator value to add.
|
125
106
|
#
|
126
107
|
# @return [ Storable ] self.
|
127
108
|
def add_logical_operator_expression(operator, op_expr)
|
@@ -145,56 +126,80 @@ module Mongoid
|
|
145
126
|
# with whatever other conditions exist.
|
146
127
|
selector.store(operator, op_expr)
|
147
128
|
else
|
148
|
-
# Other operators need to
|
149
|
-
|
150
|
-
|
151
|
-
|
129
|
+
# Other operators need to be added separately
|
130
|
+
if selector[operator]
|
131
|
+
add_logical_operator_expression('$and', [operator => op_expr])
|
132
|
+
else
|
133
|
+
selector.store(operator, op_expr)
|
134
|
+
end
|
152
135
|
end
|
153
136
|
|
154
137
|
self
|
155
138
|
end
|
156
139
|
|
157
|
-
# Adds
|
140
|
+
# Adds an operator expression to the selector.
|
158
141
|
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
142
|
+
# This method takes the operator and the operator value expression
|
143
|
+
# separately for callers' convenience. It can be considered to
|
144
|
+
# handle storing the hash `{operator => op_expr}`.
|
162
145
|
#
|
163
|
-
#
|
146
|
+
# The operator value can be of any type.
|
164
147
|
#
|
165
|
-
#
|
148
|
+
# If the selector already has the specified operator in it (on the
|
149
|
+
# top level), the new condition given in op_expr is added to the
|
150
|
+
# existing conditions for the specified operator. This is
|
151
|
+
# straightforward for $and; for other logical operators, the behavior
|
152
|
+
# of this method is to add the new conditions to the existing operator.
|
153
|
+
# For example, if the selector is currently:
|
166
154
|
#
|
167
|
-
#
|
168
|
-
#
|
155
|
+
# {'foo' => 'bar', '$or' => [{'hello' => 'world'}]}
|
156
|
+
#
|
157
|
+
# ... and operator is '$or' and op_expr is `{'test' => 123'}`,
|
158
|
+
# the resulting selector will be:
|
159
|
+
#
|
160
|
+
# {'foo' => 'bar', '$or' => [{'hello' => 'world'}, {'test' => 123}]}
|
161
|
+
#
|
162
|
+
# This does not implement an OR between the existing selector and the
|
163
|
+
# new operator expression - handling this is the job of upstream
|
164
|
+
# methods. This method simply stores op_expr into the selector on the
|
165
|
+
# assumption that the existing selector is the correct left hand side
|
166
|
+
# of the operation already.
|
167
|
+
#
|
168
|
+
# For non-logical query-level operators like $where and $text, if
|
169
|
+
# there already is a top-level operator with the same name, the
|
170
|
+
# op_expr is added to the selector via a top-level $and operator,
|
171
|
+
# thus producing a selector having both operator values.
|
172
|
+
#
|
173
|
+
# This method does not simplify values (i.e. if the selector is
|
174
|
+
# currently empty and operator is $and, op_expr is written to the
|
175
|
+
# selector with $and even if the $and can in principle be elided).
|
176
|
+
#
|
177
|
+
# This method mutates the receiver.
|
178
|
+
#
|
179
|
+
# @param [ String ] operator The operator to add.
|
180
|
+
# @param [ Object ] op_expr Operator value to add.
|
169
181
|
#
|
170
182
|
# @return [ Storable ] self.
|
171
|
-
def
|
172
|
-
unless
|
173
|
-
raise ArgumentError, "
|
183
|
+
def add_operator_expression(operator, op_expr)
|
184
|
+
unless operator.is_a?(String)
|
185
|
+
raise ArgumentError, "Operator must be a string: #{operator}"
|
174
186
|
end
|
175
187
|
|
176
|
-
|
177
|
-
raise ArgumentError, "
|
188
|
+
unless operator[0] == ?$
|
189
|
+
raise ArgumentError, "Operator must begin with $: #{operator}"
|
178
190
|
end
|
179
191
|
|
180
|
-
if
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
# Multiple operators can be combined on the same field by
|
190
|
-
# adding them to the existing hash.
|
191
|
-
new_value = selector[field].merge(value)
|
192
|
-
selector.store(field, new_value)
|
193
|
-
else
|
194
|
-
add_operator_expression('$and', [{field => value}])
|
195
|
-
end
|
192
|
+
if %w($and $nor $or).include?(operator)
|
193
|
+
return add_logical_operator_expression(operator, op_expr)
|
194
|
+
end
|
195
|
+
|
196
|
+
# For other operators, if the operator already exists in the
|
197
|
+
# query, add the new condition with $and, otherwise add the
|
198
|
+
# new condition to the top level.
|
199
|
+
if selector[operator]
|
200
|
+
add_logical_operator_expression('$and', [{operator => op_expr}])
|
196
201
|
else
|
197
|
-
selector.store(
|
202
|
+
selector.store(operator, op_expr)
|
198
203
|
end
|
199
204
|
|
200
205
|
self
|