mongoid 7.5.2 → 7.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/mongoid/association/embedded/batchable.rb +20 -3
- data/lib/mongoid/atomic/paths/embedded/many.rb +19 -0
- data/lib/mongoid/contextual/mongo.rb +24 -7
- data/lib/mongoid/shardable.rb +35 -11
- data/lib/mongoid/version.rb +1 -1
- data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +21 -0
- data/spec/mongoid/association/embedded/embeds_many_models.rb +121 -0
- data/spec/mongoid/contextual/mongo_spec.rb +23 -3
- data/spec/mongoid/shardable_models.rb +14 -0
- data/spec/mongoid/shardable_spec.rb +157 -51
- data/spec/mongoid_spec.rb +7 -1
- data/spec/shared/lib/mrss/docker_runner.rb +0 -4
- data/spec/shared/lib/mrss/lite_constraints.rb +8 -0
- data/spec/shared/shlib/server.sh +5 -5
- data.tar.gz.sig +0 -0
- metadata +659 -654
- 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: 27b0f244fe4642a684c267744270dd8e72b2b3ae1074b3621c24b31e979299b6
|
4
|
+
data.tar.gz: a79526be9f430f3777a35b2c27271175eca415c57ab2ae0346e3050b52f9311f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88c97668e4474dc109a910b2faa28f08b4c105b02e0f43611df3f2122e1232d9c3b2bfaedaf8aebebccf4358c40bc65c6bd04cfdc252c8ceca286aecf5b2d77e
|
7
|
+
data.tar.gz: ec9a56811c18fcf4cdeb238a7aa85eb350bc86513d9a883cd8f603122189af3da71bd0eea3a830ac06a26af30424648af935bf633f56ec740d045e94209b24fe
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -80,7 +80,8 @@ module Mongoid
|
|
80
80
|
def batch_replace(docs)
|
81
81
|
if docs.blank?
|
82
82
|
if _assigning? && !empty?
|
83
|
-
_base.delayed_atomic_sets.
|
83
|
+
_base.delayed_atomic_sets.delete(path)
|
84
|
+
clear_atomic_path_cache
|
84
85
|
_base.add_atomic_unset(first)
|
85
86
|
target_duplicate = _target.dup
|
86
87
|
pre_process_batch_remove(target_duplicate, :delete)
|
@@ -92,7 +93,8 @@ module Mongoid
|
|
92
93
|
_base.delayed_atomic_sets.clear unless _assigning?
|
93
94
|
docs = normalize_docs(docs).compact
|
94
95
|
_target.clear and _unscoped.clear
|
95
|
-
_base.delayed_atomic_unsets.
|
96
|
+
_base.delayed_atomic_unsets.delete(path)
|
97
|
+
clear_atomic_path_cache
|
96
98
|
inserts = execute_batch_set(docs)
|
97
99
|
add_atomic_sets(inserts)
|
98
100
|
end
|
@@ -234,7 +236,22 @@ module Mongoid
|
|
234
236
|
#
|
235
237
|
# @return [ String ] The atomic path.
|
236
238
|
def path
|
237
|
-
@path ||= _unscoped.
|
239
|
+
@path ||= if _unscoped.empty?
|
240
|
+
Mongoid::Atomic::Paths::Embedded::Many.position_without_document(_base, _association)
|
241
|
+
else
|
242
|
+
_unscoped.first.atomic_path
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Clear the cache for path and atomic_paths. This method is used when
|
247
|
+
# the path method is used, and the association has not been set on the
|
248
|
+
# document yet, which can cause path and atomic_paths to be calculated
|
249
|
+
# incorrectly later.
|
250
|
+
#
|
251
|
+
# @api private
|
252
|
+
def clear_atomic_path_cache
|
253
|
+
self.path = nil
|
254
|
+
_base.instance_variable_set("@atomic_paths", nil)
|
238
255
|
end
|
239
256
|
|
240
257
|
# Set the atomic path.
|
@@ -34,6 +34,25 @@ module Mongoid
|
|
34
34
|
locator = document.new_record? ? "" : ".#{document._index}"
|
35
35
|
"#{pos}#{"." unless pos.blank?}#{document._association.store_as}#{locator}"
|
36
36
|
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
|
40
|
+
# Get the position of where the document would go for the given
|
41
|
+
# association. The use case for this function is when trying to
|
42
|
+
# persist an empty list for an embedded association. All of the
|
43
|
+
# existing functions for getting the position to store a document
|
44
|
+
# require passing in a document to store, which we don't have when
|
45
|
+
# trying to store the empty list.
|
46
|
+
#
|
47
|
+
# @param [ Document ] parent The parent document to store in.
|
48
|
+
# @param [ Association ] association The association.
|
49
|
+
#
|
50
|
+
# @return [ String ] The position string.
|
51
|
+
def position_without_document(parent, association)
|
52
|
+
pos = parent.atomic_position
|
53
|
+
"#{pos}#{"." unless pos.blank?}#{association.store_as}"
|
54
|
+
end
|
55
|
+
end
|
37
56
|
end
|
38
57
|
end
|
39
58
|
end
|
@@ -260,16 +260,16 @@ module Mongoid
|
|
260
260
|
#
|
261
261
|
# @return [ Document ] The first document.
|
262
262
|
def first(limit_or_opts = nil)
|
263
|
-
limit = limit_or_opts
|
263
|
+
limit, opts = extract_limit_and_opts(limit_or_opts)
|
264
264
|
if cached? && cache_loaded?
|
265
265
|
return limit ? documents.first(limit) : documents.first
|
266
266
|
end
|
267
267
|
try_numbered_cache(:first, limit) do
|
268
|
-
if
|
268
|
+
if opts.key?(:id_sort)
|
269
269
|
Mongoid::Warnings.warn_id_sort_deprecated
|
270
270
|
end
|
271
271
|
sorted_view = view
|
272
|
-
if sort = view.sort || ({ _id: 1 } unless
|
272
|
+
if sort = view.sort || ({ _id: 1 } unless opts[:id_sort] == :none)
|
273
273
|
sorted_view = view.sort(sort)
|
274
274
|
end
|
275
275
|
if raw_docs = sorted_view.limit(limit || 1).to_a
|
@@ -376,12 +376,12 @@ module Mongoid
|
|
376
376
|
#
|
377
377
|
# @return [ Document ] The last document.
|
378
378
|
def last(limit_or_opts = nil)
|
379
|
-
limit = limit_or_opts
|
379
|
+
limit, opts = extract_limit_and_opts(limit_or_opts)
|
380
380
|
if cached? && cache_loaded?
|
381
381
|
return limit ? documents.last(limit) : documents.last
|
382
382
|
end
|
383
383
|
res = try_numbered_cache(:last, limit) do
|
384
|
-
with_inverse_sorting(
|
384
|
+
with_inverse_sorting(opts) do
|
385
385
|
if raw_docs = view.limit(limit || 1).to_a
|
386
386
|
process_raw_docs(raw_docs, limit)
|
387
387
|
end
|
@@ -612,6 +612,23 @@ module Mongoid
|
|
612
612
|
end
|
613
613
|
end
|
614
614
|
|
615
|
+
# Extract the limit and opts from the given argument, so that code
|
616
|
+
# can operate without having to worry about the current type and
|
617
|
+
# state of the argument.
|
618
|
+
#
|
619
|
+
# @param [ nil | Integer | Hash ] limit_or_opts The value to pull the
|
620
|
+
# limit and option hash from.
|
621
|
+
#
|
622
|
+
# @return [ Array<nil | Integer, Hash> ] A 2-array of the limit and the
|
623
|
+
# option hash.
|
624
|
+
def extract_limit_and_opts(limit_or_opts)
|
625
|
+
case limit_or_opts
|
626
|
+
when nil, Integer then [ limit_or_opts, {} ]
|
627
|
+
when Hash then [ nil, limit_or_opts ]
|
628
|
+
else raise ArgumentError, "expected nil, Integer, or Hash"
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
615
632
|
# Update the documents for the provided method.
|
616
633
|
#
|
617
634
|
# @api private
|
@@ -676,10 +693,10 @@ module Mongoid
|
|
676
693
|
# @example Apply the inverse sorting params to the given block
|
677
694
|
# context.with_inverse_sorting
|
678
695
|
def with_inverse_sorting(opts = {})
|
679
|
-
Mongoid::Warnings.warn_id_sort_deprecated if opts.
|
696
|
+
Mongoid::Warnings.warn_id_sort_deprecated if opts.key?(:id_sort)
|
680
697
|
|
681
698
|
begin
|
682
|
-
if sort = criteria.options[:sort] || ( { _id: 1 } unless opts
|
699
|
+
if sort = criteria.options[:sort] || ( { _id: 1 } unless opts[:id_sort] == :none )
|
683
700
|
@view = view.sort(Hash[sort.map{|k, v| [k, -1*v]}])
|
684
701
|
end
|
685
702
|
yield
|
data/lib/mongoid/shardable.rb
CHANGED
@@ -47,18 +47,22 @@ module Mongoid
|
|
47
47
|
self.class.shard_key_fields
|
48
48
|
end
|
49
49
|
|
50
|
-
# Returns the selector that would match the
|
51
|
-
#
|
50
|
+
# Returns the selector that would match the defined shard keys. If
|
51
|
+
# `prefer_persisted` is false (the default), it uses the current values
|
52
|
+
# of the specified shard keys, otherwise, it will try to use whatever value
|
53
|
+
# was most recently persisted.
|
54
|
+
#
|
55
|
+
# @param [ true | false ] prefer_persisted Whether to use the current
|
56
|
+
# value of the shard key fields, or to use their most recently persisted
|
57
|
+
# values.
|
52
58
|
#
|
53
59
|
# @return [ Hash ] The shard key selector.
|
54
60
|
#
|
55
61
|
# @api private
|
56
|
-
def shard_key_selector
|
57
|
-
|
58
|
-
|
59
|
-
selector[field.to_s] = send(field)
|
62
|
+
def shard_key_selector(prefer_persisted: false)
|
63
|
+
shard_key_fields.each_with_object({}) do |field, selector|
|
64
|
+
selector[field.to_s] = shard_key_field_value(field.to_s, prefer_persisted: prefer_persisted)
|
60
65
|
end
|
61
|
-
selector
|
62
66
|
end
|
63
67
|
|
64
68
|
# Returns the selector that would match the existing version of this
|
@@ -72,11 +76,31 @@ module Mongoid
|
|
72
76
|
#
|
73
77
|
# @api private
|
74
78
|
def shard_key_selector_in_db
|
75
|
-
|
76
|
-
|
77
|
-
|
79
|
+
shard_key_selector(prefer_persisted: true)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the value for the named shard key. If the field identifies
|
83
|
+
# an embedded document, the key will be parsed and recursively evaluated.
|
84
|
+
# If `prefer_persisted` is true, the value last persisted to the database
|
85
|
+
# will be returned, regardless of what the current value of the attribute
|
86
|
+
# may be.
|
87
|
+
#
|
88
|
+
# @param [String] field The name of the field to evaluate
|
89
|
+
# @param [ true|false ] prefer_persisted Whether or not to prefer the
|
90
|
+
# persisted value over the current value.
|
91
|
+
#
|
92
|
+
# @return [ Object ] The value of the named field.
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def shard_key_field_value(field, prefer_persisted:)
|
96
|
+
if field.include?(".")
|
97
|
+
relation, remaining = field.split(".", 2)
|
98
|
+
send(relation)&.shard_key_field_value(remaining, prefer_persisted: prefer_persisted)
|
99
|
+
elsif prefer_persisted && !new_record?
|
100
|
+
attribute_was(field)
|
101
|
+
else
|
102
|
+
send(field)
|
78
103
|
end
|
79
|
-
selector
|
80
104
|
end
|
81
105
|
|
82
106
|
module ClassMethods
|
data/lib/mongoid/version.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "spec_helper"
|
4
|
+
require_relative '../embeds_many_models.rb'
|
4
5
|
|
5
6
|
describe Mongoid::Association::Embedded::EmbedsMany::Proxy do
|
6
7
|
|
@@ -4649,4 +4650,24 @@ describe Mongoid::Association::Embedded::EmbedsMany::Proxy do
|
|
4649
4650
|
end
|
4650
4651
|
end
|
4651
4652
|
end
|
4653
|
+
|
4654
|
+
context "when using assign_attributes with an already populated array" do
|
4655
|
+
let(:post) { EmmPost.create! }
|
4656
|
+
|
4657
|
+
before do
|
4658
|
+
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'a'}],
|
4659
|
+
user_tags: [{id: BSON::ObjectId.new, title: 'b'}])
|
4660
|
+
post.save!
|
4661
|
+
post.reload
|
4662
|
+
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'c'}],
|
4663
|
+
user_tags: [])
|
4664
|
+
post.save!
|
4665
|
+
post.reload
|
4666
|
+
end
|
4667
|
+
|
4668
|
+
it "has the correct embedded documents" do
|
4669
|
+
expect(post.company_tags.length).to eq(1)
|
4670
|
+
expect(post.company_tags.first.title).to eq("c")
|
4671
|
+
end
|
4672
|
+
end
|
4652
4673
|
end
|
@@ -67,3 +67,124 @@ class EmmOuter
|
|
67
67
|
|
68
68
|
field :level, :type => Integer
|
69
69
|
end
|
70
|
+
|
71
|
+
class EmmCustomerAddress
|
72
|
+
include Mongoid::Document
|
73
|
+
|
74
|
+
embedded_in :addressable, polymorphic: true, inverse_of: :work_address
|
75
|
+
end
|
76
|
+
|
77
|
+
class EmmFriend
|
78
|
+
include Mongoid::Document
|
79
|
+
|
80
|
+
embedded_in :befriendable, polymorphic: true
|
81
|
+
end
|
82
|
+
|
83
|
+
class EmmCustomer
|
84
|
+
include Mongoid::Document
|
85
|
+
|
86
|
+
embeds_one :home_address, class_name: 'EmmCustomerAddress', as: :addressable
|
87
|
+
embeds_one :work_address, class_name: 'EmmCustomerAddress', as: :addressable
|
88
|
+
|
89
|
+
embeds_many :close_friends, class_name: 'EmmFriend', as: :befriendable
|
90
|
+
embeds_many :acquaintances, class_name: 'EmmFriend', as: :befriendable
|
91
|
+
end
|
92
|
+
|
93
|
+
class EmmUser
|
94
|
+
include Mongoid::Document
|
95
|
+
include Mongoid::Timestamps
|
96
|
+
|
97
|
+
embeds_many :orders, class_name: 'EmmOrder'
|
98
|
+
end
|
99
|
+
|
100
|
+
class EmmOrder
|
101
|
+
include Mongoid::Document
|
102
|
+
|
103
|
+
field :amount, type: Integer
|
104
|
+
|
105
|
+
embedded_in :user, class_name: 'EmmUser'
|
106
|
+
end
|
107
|
+
|
108
|
+
module EmmSpec
|
109
|
+
# There is also a top-level Car class defined.
|
110
|
+
class Car
|
111
|
+
include Mongoid::Document
|
112
|
+
|
113
|
+
embeds_many :doors
|
114
|
+
end
|
115
|
+
|
116
|
+
class Door
|
117
|
+
include Mongoid::Document
|
118
|
+
|
119
|
+
embedded_in :car
|
120
|
+
end
|
121
|
+
|
122
|
+
class Tank
|
123
|
+
include Mongoid::Document
|
124
|
+
|
125
|
+
embeds_many :guns
|
126
|
+
embeds_many :emm_turrets
|
127
|
+
# This association references a model that is not in our module,
|
128
|
+
# and it does not define class_name hence Mongoid will not be able to
|
129
|
+
# figure out the inverse for this association.
|
130
|
+
embeds_many :emm_hatches
|
131
|
+
|
132
|
+
# class_name is intentionally unqualified, references a class in the
|
133
|
+
# same module. Rails permits class_name to be unqualified like this.
|
134
|
+
embeds_many :launchers, class_name: 'Launcher'
|
135
|
+
end
|
136
|
+
|
137
|
+
class Gun
|
138
|
+
include Mongoid::Document
|
139
|
+
|
140
|
+
embedded_in :tank
|
141
|
+
end
|
142
|
+
|
143
|
+
class Launcher
|
144
|
+
include Mongoid::Document
|
145
|
+
|
146
|
+
# class_name is intentionally unqualified.
|
147
|
+
embedded_in :tank, class_name: 'Tank'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# This is intentionally on top level.
|
152
|
+
class EmmTurret
|
153
|
+
include Mongoid::Document
|
154
|
+
|
155
|
+
embedded_in :tank, class_name: 'EmmSpec::Tank'
|
156
|
+
end
|
157
|
+
|
158
|
+
# This is intentionally on top level.
|
159
|
+
class EmmHatch
|
160
|
+
include Mongoid::Document
|
161
|
+
|
162
|
+
# No :class_name option on this association intentionally.
|
163
|
+
embedded_in :tank
|
164
|
+
end
|
165
|
+
|
166
|
+
class EmmPost
|
167
|
+
include Mongoid::Document
|
168
|
+
|
169
|
+
embeds_many :company_tags, class_name: "EmmCompanyTag"
|
170
|
+
embeds_many :user_tags, class_name: "EmmUserTag"
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
class EmmCompanyTag
|
175
|
+
include Mongoid::Document
|
176
|
+
|
177
|
+
field :title, type: String
|
178
|
+
|
179
|
+
embedded_in :post, class_name: "EmmPost"
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
class EmmUserTag
|
184
|
+
include Mongoid::Document
|
185
|
+
|
186
|
+
field :title, type: String
|
187
|
+
|
188
|
+
embedded_in :post, class_name: "EmmPost"
|
189
|
+
end
|
190
|
+
|
@@ -1554,9 +1554,9 @@ describe Mongoid::Contextual::Mongo do
|
|
1554
1554
|
expect(context.send(method)).to eq(depeche_mode)
|
1555
1555
|
end
|
1556
1556
|
|
1557
|
-
context
|
1557
|
+
context "when calling ##{method}" do
|
1558
1558
|
|
1559
|
-
it 'returns the
|
1559
|
+
it 'returns the requested document, sorted by _id' do
|
1560
1560
|
expect(context.send(method)).to eq(depeche_mode)
|
1561
1561
|
expect(context.last).to eq(rolling_stones)
|
1562
1562
|
end
|
@@ -1571,7 +1571,7 @@ describe Mongoid::Contextual::Mongo do
|
|
1571
1571
|
expect(context.send(method, opts)).to eq(depeche_mode)
|
1572
1572
|
end
|
1573
1573
|
|
1574
|
-
context
|
1574
|
+
context "when calling ##{method}" do
|
1575
1575
|
|
1576
1576
|
it 'doesn\'t apply a sort on _id' do
|
1577
1577
|
expect(context.send(method, opts)).to eq(depeche_mode)
|
@@ -1883,6 +1883,16 @@ describe Mongoid::Contextual::Mongo do
|
|
1883
1883
|
end
|
1884
1884
|
end
|
1885
1885
|
|
1886
|
+
context "when given an empty hash" do
|
1887
|
+
let(:context) { described_class.new(criteria) }
|
1888
|
+
let(:criteria) { Band.criteria }
|
1889
|
+
let(:docs) { context.send(method, {}) }
|
1890
|
+
|
1891
|
+
it "behaves as if limit is nil" do
|
1892
|
+
expect(docs).to eq(depeche_mode)
|
1893
|
+
end
|
1894
|
+
end
|
1895
|
+
|
1886
1896
|
context "when calling #first then #last" do
|
1887
1897
|
|
1888
1898
|
let(:context) do
|
@@ -2358,6 +2368,16 @@ describe Mongoid::Contextual::Mongo do
|
|
2358
2368
|
end
|
2359
2369
|
end
|
2360
2370
|
end
|
2371
|
+
|
2372
|
+
context "when given an empty hash" do
|
2373
|
+
let(:context) { described_class.new(criteria) }
|
2374
|
+
let(:criteria) { Band.criteria }
|
2375
|
+
let(:docs) { context.last({}) }
|
2376
|
+
|
2377
|
+
it "behaves as if limit is nil" do
|
2378
|
+
expect(docs).to eq(rolling_stones)
|
2379
|
+
end
|
2380
|
+
end
|
2361
2381
|
end
|
2362
2382
|
|
2363
2383
|
describe "#initialize" do
|
@@ -59,3 +59,17 @@ end
|
|
59
59
|
class SmNotSharded
|
60
60
|
include Mongoid::Document
|
61
61
|
end
|
62
|
+
|
63
|
+
class SmReviewAuthor
|
64
|
+
include Mongoid::Document
|
65
|
+
embedded_in :review, class_name: "SmReview", touch: false
|
66
|
+
field :name, type: String
|
67
|
+
end
|
68
|
+
|
69
|
+
class SmReview
|
70
|
+
include Mongoid::Document
|
71
|
+
|
72
|
+
embeds_one :author, class_name: "SmReviewAuthor"
|
73
|
+
|
74
|
+
shard_key "author.name" => 1
|
75
|
+
end
|