mongoid 6.4.1 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4f048aa8781f60c32235fe9d3daee1cfa17e7e58
4
- data.tar.gz: 1fdca10737525f1272fbcde4273452c9d0ea116c
2
+ SHA256:
3
+ metadata.gz: 4c5d349e3de6af570dd7b1347fd76b6056848149180dc355e116f67f8ac2c3c6
4
+ data.tar.gz: e9be28598f701bb124ba6626bb777da07d05bca692224110078700557f2c0aa9
5
5
  SHA512:
6
- metadata.gz: 191be89fce8a8d7437070a526b68ab6cd9622235d40ac8d01d22dfc477914bc4eec23a28056fde69c2a5651c36a9d2bfea84765d3dbf1f7b6367de941aa73778
7
- data.tar.gz: 4e6371d1ae1d2fac3ff3c16d0283ac04132a9d09c83d390e8c3ae714a62ed833897f29d6ff0f2b1de9b6ae90b1019b0ae72d68752584792fbe62536db3a2e54a
6
+ metadata.gz: 1bf33975f8969cc06ac717cef1a443429dd6ad8e8a7cd755c8b74ed9b7bc0e1a113413dac81098f54f9c181b91d03fc5958adfaaddb03d7fb91caa9f2e11dc84
7
+ data.tar.gz: c5ef0576ed9941a5ba0f0865fdaff885766e4d6fe6901d1037ce408a0acdc0e00fdbfe503a5a7d721d8178a83c39c6e5893aaf0c258e871b3dca17939d0e7d97
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -165,7 +165,7 @@ module Mongoid
165
165
  def raw
166
166
  validate_out!
167
167
  cmd = command
168
- opts = { read: cmd.delete(:read).options } if cmd[:read]
168
+ opts = { read: cmd.delete(:read) } if cmd[:read]
169
169
  @map_reduce.database.command(cmd, (opts || {}).merge(session: _session)).first
170
170
  end
171
171
  alias :results :raw
@@ -3,6 +3,10 @@ module Mongoid
3
3
  class Criteria
4
4
  module Modifiable
5
5
 
6
+ # @attribute [r] create_attrs Additional attributes to add to the Document upon creation.
7
+ # @api private
8
+ attr_reader :create_attrs
9
+
6
10
  # Build a document given the selector and return it.
7
11
  # Complex criteria, such as $in and $or operations will get ignored.
8
12
  #
@@ -57,6 +61,9 @@ module Mongoid
57
61
 
58
62
  # Define attributes with which new documents will be created.
59
63
  #
64
+ # Note that if `find_or_create_by` is called after this in a method chain, the attributes in
65
+ # the query will override those from this method.
66
+ #
60
67
  # @example Define attributes to be used when a new document is created.
61
68
  # Person.create_with(job: 'Engineer').find_or_create_by(employer: 'MongoDB')
62
69
  #
@@ -64,7 +71,9 @@ module Mongoid
64
71
  #
65
72
  # @since 5.1.0
66
73
  def create_with(attrs = {})
67
- where(selector.merge(attrs))
74
+ tap do
75
+ (@create_attrs ||= {}).merge!(attrs)
76
+ end
68
77
  end
69
78
 
70
79
  # Find the first +Document+ given the conditions, or creates a new document
@@ -172,7 +181,8 @@ module Mongoid
172
181
  #
173
182
  # @since 3.0.0
174
183
  def create_document(method, attrs = nil, &block)
175
- attributes = selector.reduce(attrs ? attrs.dup : {}) do |hash, (key, value)|
184
+ attrs = (create_attrs || {}).merge(attrs || {})
185
+ attributes = selector.reduce(attrs) do |hash, (key, value)|
176
186
  unless invalid_key?(hash, key) || invalid_embedded_doc?(value)
177
187
  hash[key] = value
178
188
  end
@@ -24,7 +24,7 @@ module Mongoid
24
24
  # @since 2.0.0
25
25
  POLYGON = "Polygon"
26
26
 
27
- # @attribute [rw] negating If the next spression is negated.
27
+ # @attribute [rw] negating If the next expression is negated.
28
28
  # @attribute [rw] selector The query selector.
29
29
  attr_accessor :negating, :selector
30
30
 
@@ -11,6 +11,7 @@ require "mongoid/matchable/lte"
11
11
  require "mongoid/matchable/ne"
12
12
  require "mongoid/matchable/nin"
13
13
  require "mongoid/matchable/or"
14
+ require "mongoid/matchable/nor"
14
15
  require "mongoid/matchable/size"
15
16
  require "mongoid/matchable/elem_match"
16
17
  require "mongoid/matchable/regexp"
@@ -40,6 +41,7 @@ module Mongoid
40
41
  "$ne" => Ne,
41
42
  "$nin" => Nin,
42
43
  "$or" => Or,
44
+ "$nor" => Nor,
43
45
  "$size" => Size
44
46
  }.with_indifferent_access.freeze
45
47
 
@@ -124,6 +126,7 @@ module Mongoid
124
126
  case key.to_s
125
127
  when "$or" then Or.new(value, document)
126
128
  when "$and" then And.new(value, document)
129
+ when "$nor" then Nor.new(value, document)
127
130
  else Default.new(extract_attribute(document, key))
128
131
  end
129
132
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+ module Mongoid
4
+ module Matchable
5
+
6
+ # Defines behavior for handling $nor expressions in embedded documents.
7
+ class Nor < Default
8
+
9
+ # Does the supplied query match the attribute?
10
+ #
11
+ # Note: an empty array as an argument to $nor is prohibited by
12
+ # MongoDB server. Mongoid does allow this and returns false in this case.
13
+ #
14
+ # @example Does this match?
15
+ # matcher._matches?("$nor" => [ { field => value } ])
16
+ #
17
+ # @param [ Array ] conditions The or expression.
18
+ #
19
+ # @return [ true, false ] True if matches, false if not.
20
+ #
21
+ # @since 6.4.2/7.0.2/7.1.0
22
+ def _matches?(conditions)
23
+ if conditions.length == 0
24
+ # MongoDB does not allow $nor array to be empty, but
25
+ # Mongoid accepts an empty array for $or which MongoDB also
26
+ # prohibits
27
+ return false
28
+ end
29
+ conditions.none? do |condition|
30
+ condition.all? do |key, value|
31
+ document._matches?(key => value)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -25,15 +25,15 @@ module Mongoid
25
25
 
26
26
  field_and_value_hash = hasherizer(field.split('.'), value)
27
27
  field = field_and_value_hash.keys.first.to_s
28
+ value = field_and_value_hash[field]
28
29
 
29
- if fields[field] && fields[field].type == Hash && attributes.key?(field) && !value.empty?
30
+ if fields[field] && fields[field].type == Hash && attributes.key?(field) && Hash === value && !value.empty?
30
31
  merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
31
- value = (attributes[field] || {}).merge(field_and_value_hash[field], &merger)
32
- process_attribute(field.to_s, value)
33
- else
34
- process_attribute(field.to_s, field_and_value_hash[field])
32
+ value = (attributes[field] || {}).merge(value, &merger)
35
33
  end
36
34
 
35
+ process_attribute(field.to_s, value)
36
+
37
37
  unless relations.include?(field.to_s)
38
38
  ops[atomic_attribute_name(field)] = attributes[field]
39
39
  end
@@ -107,6 +107,10 @@ module Mongoid
107
107
  #
108
108
  # @since 6.0.0
109
109
  def client
110
+ client_options = send(:client_options)
111
+ if client_options[:read].is_a?(Symbol)
112
+ client_options = client_options.merge(read: {mode: client_options[:read]})
113
+ end
110
114
  @client ||= (client = Clients.with_name(client_name)
111
115
  client = client.use(database_name) if database_name_option
112
116
  client.with(client_options))
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "6.4.1"
3
+ VERSION = "6.4.2"
4
4
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ArrayField
4
+ include Mongoid::Document
5
+
6
+ field :af, type: Array
7
+ end
@@ -132,8 +132,8 @@ describe Mongoid::Clients::Factory do
132
132
 
133
133
  let(:config) do
134
134
  {
135
- default: { hosts: [ "127.0.0.1:27017" ], database: database_id },
136
- secondary: { uri: "mongodb://127.0.0.1:27017,127.0.0.1:27018/mongoid_test" }
135
+ default: { hosts: [ "127.0.0.1:1234" ], database: database_id },
136
+ secondary: { uri: "mongodb://127.0.0.1:1234,127.0.0.1:5678/mongoid_test" }
137
137
  }
138
138
  end
139
139
 
@@ -162,7 +162,7 @@ describe Mongoid::Clients::Factory do
162
162
  end
163
163
 
164
164
  it "sets the cluster's seeds" do
165
- expect(seeds).to eq([ "127.0.0.1:27017", "127.0.0.1:27018" ])
165
+ expect(seeds).to eq([ "127.0.0.1:1234", "127.0.0.1:5678" ])
166
166
  end
167
167
  end
168
168
  end
@@ -177,25 +177,40 @@ describe Mongoid::Clients::Options do
177
177
 
178
178
  context 'when returning a criteria' do
179
179
 
180
- let(:context_and_criteria) do
181
- collection = nil
182
- cxt = Band.with(read: :secondary) do |klass|
183
- collection = klass.all.collection
184
- klass.persistence_context
180
+ shared_context 'applies secondary read preference' do
181
+
182
+ let(:context_and_criteria) do
183
+ collection = nil
184
+ cxt = Band.with(read_secondary_option) do |klass|
185
+ collection = klass.all.collection
186
+ klass.persistence_context
187
+ end
188
+ [ cxt, collection ]
185
189
  end
186
- [ cxt, collection ]
187
- end
188
190
 
189
- let(:persistence_context) do
190
- context_and_criteria[0]
191
+ let(:persistence_context) do
192
+ context_and_criteria[0]
193
+ end
194
+
195
+ let(:client) do
196
+ context_and_criteria[1].client
197
+ end
198
+
199
+ it 'applies the options to the criteria client' do
200
+ expect(client.options['read']).to eq('mode' => :secondary)
201
+ end
191
202
  end
192
203
 
193
- let(:client) do
194
- context_and_criteria[1].client
204
+ context 'read: :secondary shorthand' do
205
+ let(:read_secondary_option) { {read: :secondary} }
206
+
207
+ it_behaves_like 'applies secondary read preference'
195
208
  end
196
209
 
197
- it 'applies the options to the criteria client' do
198
- expect(client.options['read']).to eq(:secondary)
210
+ context 'read: {mode: :secondary}' do
211
+ let(:read_secondary_option) { {read: {mode: :secondary}} }
212
+
213
+ it_behaves_like 'applies secondary read preference'
199
214
  end
200
215
  end
201
216
 
@@ -16,17 +16,17 @@ describe Mongoid::Clients::Sessions do
16
16
  end
17
17
 
18
18
  let(:subscriber) do
19
- Mongoid::Clients.with_name(:other).instance_variable_get(:@monitoring).subscribers['Command'].find do |s|
19
+ Mongoid::Clients.with_name(:other).send(:monitoring).subscribers['Command'].find do |s|
20
20
  s.is_a?(EventSubscriber)
21
21
  end
22
22
  end
23
23
 
24
24
  let(:insert_events) do
25
- subscriber.started_events.select { |event| event.command_name == :insert }
25
+ subscriber.started_events.select { |event| event.command_name == 'insert' }
26
26
  end
27
27
 
28
28
  let(:update_events) do
29
- subscriber.started_events.select { |event| event.command_name == :update }
29
+ subscriber.started_events.select { |event| event.command_name == 'update' }
30
30
  end
31
31
 
32
32
  context 'when a session is used on a model class' do
@@ -1471,11 +1471,15 @@ describe Mongoid::Criteria::Modifiable do
1471
1471
  { 'username' => 'Turnip' }
1472
1472
  end
1473
1473
 
1474
- it 'returns a criteria with the defined attributes' do
1475
- expect(Person.create_with(attrs).selector).to eq(attrs)
1474
+ it 'does not modify the selector' do
1475
+ expect(Person.create_with(attrs).selector[:username]).to be_nil
1476
1476
  end
1477
1477
 
1478
- context 'when a method is chained' do
1478
+ it 'create_attrs is modified' do
1479
+ expect(Person.create_with(attrs).create_attrs).to eq(attrs)
1480
+ end
1481
+
1482
+ context 'when a create is chained' do
1479
1483
 
1480
1484
  context 'when a write method is chained' do
1481
1485
 
@@ -1499,6 +1503,25 @@ describe Mongoid::Criteria::Modifiable do
1499
1503
  expect(new_person.age).to eq(50)
1500
1504
  end
1501
1505
 
1506
+ context 'when a matching document is already in the collection' do
1507
+ let(:query) do
1508
+ { 'username' => 'foo', 'age' => 12 }
1509
+ end
1510
+
1511
+ let(:person) do
1512
+ Person.create!(query)
1513
+ end
1514
+
1515
+ let(:found_person) do
1516
+ Person.create_with(attrs).find_or_create_by(query)
1517
+ end
1518
+
1519
+ it 'finds the matching document' do
1520
+ person
1521
+ expect(found_person.id).to eq(person.id)
1522
+ end
1523
+ end
1524
+
1502
1525
  context 'when the attributes are shared with the write method args' do
1503
1526
 
1504
1527
  let(:query) do
@@ -1509,7 +1532,7 @@ describe Mongoid::Criteria::Modifiable do
1509
1532
  Person.create_with(attrs).find_or_create_by(query)
1510
1533
  end
1511
1534
 
1512
- it 'gives the write method args precedence' do
1535
+ it 'gives the find method args precedence' do
1513
1536
  expect(new_person.username).to eq('Beet')
1514
1537
  expect(new_person.age).to eq(50)
1515
1538
  end
@@ -1536,8 +1559,12 @@ describe Mongoid::Criteria::Modifiable do
1536
1559
  { 'username' => 'Beet', 'age' => 50 }
1537
1560
  end
1538
1561
 
1562
+ it 'does not modify the selector' do
1563
+ expect(criteria.create_with(attrs).selector).to eq(criteria_selector)
1564
+ end
1565
+
1539
1566
  it 'overwrites all the original attributes' do
1540
- expect(criteria.create_with(attrs).selector).to eq(attrs)
1567
+ expect(criteria.create_with(attrs).create_attrs).to eq(attrs)
1541
1568
  end
1542
1569
  end
1543
1570
  end
@@ -1548,8 +1575,12 @@ describe Mongoid::Criteria::Modifiable do
1548
1575
  { 'username' => 'Beet' }
1549
1576
  end
1550
1577
 
1578
+ it 'does not modify the selector' do
1579
+ expect(criteria.create_with(attrs).selector).to eq(criteria_selector)
1580
+ end
1581
+
1551
1582
  it 'only overwrites the shared attributes' do
1552
- expect(criteria.create_with(attrs).selector).to eq(criteria_selector.merge!(attrs))
1583
+ expect(criteria.create_with(attrs).create_attrs).to eq(attrs)
1553
1584
  end
1554
1585
  end
1555
1586
 
@@ -1558,12 +1589,11 @@ describe Mongoid::Criteria::Modifiable do
1558
1589
  let(:attrs) do
1559
1590
  { 'username' => 'Turnip' }
1560
1591
  end
1561
-
1562
1592
  let(:query) do
1563
1593
  { 'username' => 'Beet', 'age' => 50 }
1564
1594
  end
1565
1595
 
1566
- context 'when a write method is chained' do
1596
+ context 'when a create method is chained' do
1567
1597
 
1568
1598
  it 'executes the method' do
1569
1599
  expect(criteria.create_with(attrs).new.username).to eq('Turnip')
@@ -1577,9 +1607,28 @@ describe Mongoid::Criteria::Modifiable do
1577
1607
  criteria.create_with(attrs).find_or_create_by(query)
1578
1608
  end
1579
1609
 
1580
- it 'executes the query' do
1610
+ it 'gives the find method arg precedence' do
1581
1611
  expect(new_person.username).to eq('Beet')
1582
- expect(new_person.age).to eq(50)
1612
+ expect(new_person.age).to be(50)
1613
+ end
1614
+
1615
+ context 'when a matching document is already in the collection' do
1616
+ let(:query) do
1617
+ { 'username' => 'foo', 'age' => 12 }
1618
+ end
1619
+
1620
+ let(:person) do
1621
+ Person.create!(query)
1622
+ end
1623
+
1624
+ let(:found_person) do
1625
+ criteria.create_with(attrs).find_or_create_by(query)
1626
+ end
1627
+
1628
+ it 'finds the matching document' do
1629
+ person
1630
+ expect(found_person.id).to eq(person.id)
1631
+ end
1583
1632
  end
1584
1633
  end
1585
1634
  end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Mongoid::Matchable::Nor do
6
+
7
+ let(:target) do
8
+ Person.new
9
+ end
10
+
11
+ let(:matcher) do
12
+ described_class.new("value", target)
13
+ end
14
+
15
+ describe "#_matches?" do
16
+
17
+ context "when provided a simple expression" do
18
+
19
+ context "when one of the hashes does not match model" do
20
+
21
+ let(:matches) do
22
+ matcher._matches?(
23
+ [ { title: "Sir" }, { title: "King" } ]
24
+ )
25
+ end
26
+
27
+ let(:target) do
28
+ Person.new(title: 'Queen')
29
+ end
30
+
31
+ it "returns true" do
32
+ expect(matches).to be true
33
+ end
34
+ end
35
+
36
+ context "when all of the hashes match different fields in model" do
37
+ let(:matches) do
38
+ matcher._matches?(
39
+ [ { age: 10 }, { title: "King" } ]
40
+ )
41
+ end
42
+
43
+ let(:target) do
44
+ Person.new(title: 'King', age: 10)
45
+ end
46
+
47
+ it "returns false" do
48
+ expect(matches).to be false
49
+ end
50
+ end
51
+
52
+ context "when one of the hashes matches an array field in model" do
53
+ let(:matches) do
54
+ matcher._matches?(
55
+ [ { af: "Sir" }, { af: "King" } ]
56
+ )
57
+ end
58
+
59
+ let(:target) do
60
+ ArrayField.new(af: ['King'])
61
+ end
62
+
63
+ it "returns false" do
64
+ expect(matches).to be false
65
+ end
66
+ end
67
+
68
+ context "when none of the hashes matches an array field in model" do
69
+ let(:matches) do
70
+ matcher._matches?(
71
+ [ { af: "Sir" }, { af: "King" } ]
72
+ )
73
+ end
74
+
75
+ let(:target) do
76
+ ArrayField.new(af: ['Boo'])
77
+ end
78
+
79
+ it "returns true" do
80
+ expect(matches).to be true
81
+ end
82
+ end
83
+
84
+ context "when there are no criteria" do
85
+
86
+ it "returns false" do
87
+ expect(matcher._matches?([])).to be false
88
+ end
89
+ end
90
+
91
+ # $nor with $not is a double negation.
92
+ # Whatever the argument of $not is is what the overall condition
93
+ # is looking for.
94
+ context "when the expression is a $not" do
95
+
96
+ let(:matches) do
97
+ matcher._matches?([ { title: {:$not => /Foobar/ } }])
98
+ end
99
+
100
+ context "when the value does not match $not argument" do
101
+
102
+ let(:target) do
103
+ Person.new(title: 'test')
104
+ end
105
+
106
+ it "returns false" do
107
+ expect(matches).to be false
108
+ end
109
+ end
110
+
111
+ context "when the value matches $not argument" do
112
+
113
+ let(:target) do
114
+ Person.new(title: 'Foobar baz')
115
+ end
116
+
117
+ it "returns true" do
118
+ expect(matches).to be true
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ context "when provided a complex expression" do
125
+
126
+ context "when none of the model values match criteria values" do
127
+
128
+ let(:matches) do
129
+ matcher._matches?(
130
+ [
131
+ { title: { "$in" => [ "Sir", "Madam" ] } },
132
+ { title: "King" }
133
+ ]
134
+ )
135
+ end
136
+
137
+ let(:target) do
138
+ Person.new(title: 'Queen')
139
+ end
140
+
141
+ it "returns true" do
142
+ expect(matches).to be true
143
+ end
144
+ end
145
+
146
+ context "when there is a matching value" do
147
+
148
+ let(:matches) do
149
+ matcher._matches?(
150
+ [
151
+ { title: { "$in" => [ "Prince", "Madam" ] } },
152
+ { title: "King" }
153
+ ]
154
+ )
155
+ end
156
+
157
+ let(:target) do
158
+ Person.new(title: 'Prince')
159
+ end
160
+
161
+ it "returns false" do
162
+ expect(matches).to be false
163
+ end
164
+ end
165
+
166
+ context "when expression contain multiple fields" do
167
+
168
+ let(:matches) do
169
+ matcher._matches?(
170
+ [
171
+ { title: "Sir", age: 23 },
172
+ { title: "King", age: 100 }
173
+ ]
174
+ )
175
+ end
176
+
177
+ context 'and model has different values in all of the fields' do
178
+ let(:target) do
179
+ Person.new(title: 'Queen', age: 10)
180
+ end
181
+
182
+ it "returns true" do
183
+ expect(matches).to be true
184
+ end
185
+ end
186
+
187
+ context 'and model has identical value in one of the fields' do
188
+ let(:target) do
189
+ Person.new(title: 'Queen', age: 23)
190
+ end
191
+
192
+ it "returns true" do
193
+ expect(matches).to be true
194
+ end
195
+ end
196
+
197
+ context 'and model has identical values in all of the fields' do
198
+ let(:target) do
199
+ Person.new(title: 'Sir', age: 23)
200
+ end
201
+
202
+ it "returns false" do
203
+ expect(matches).to be false
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end