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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e67e9eccd518e5c987bbca2d4943a461d7ceaf382abd091842806fb300dffb1e
4
- data.tar.gz: 441f04f551b51c8b5f3300e4158fd7d0b6b0c3406b39dfa073bf1ce11cead324
3
+ metadata.gz: 27b0f244fe4642a684c267744270dd8e72b2b3ae1074b3621c24b31e979299b6
4
+ data.tar.gz: a79526be9f430f3777a35b2c27271175eca415c57ab2ae0346e3050b52f9311f
5
5
  SHA512:
6
- metadata.gz: 31d1d76cbe4923b0cb2a54a62d3758183a80ef8c8833448760115d2299153724e8cb2e6160745989357f6a4bbd3e6873380e73f500e13ffb14b307dfa503a3a9
7
- data.tar.gz: 9c3acad387939c0bfe4746102e73e24124597bf311adbd3ce301bfe9a6882a066dadbb67ccb05a470a983c6b0d55ca43cce01f247111cd9d64efb2653614fbd8
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.clear
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.clear
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.first.atomic_path
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 unless limit_or_opts.is_a?(Hash)
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 limit_or_opts.try(:key?, :id_sort)
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 limit_or_opts.try(:fetch, :id_sort) == :none)
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 unless limit_or_opts.is_a?(Hash)
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(limit_or_opts) do
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.try(:key?, :id_sort)
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.try(:fetch, :id_sort) == :none )
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
@@ -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 current version of this
51
- # document.
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
- selector = {}
58
- shard_key_fields.each do |field|
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
- selector = {}
76
- shard_key_fields.each do |field|
77
- selector[field.to_s] = new_record? ? send(field) : attribute_was(field)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "7.5.2"
4
+ VERSION = "7.5.3"
5
5
  end
@@ -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 'when calling #last' do
1557
+ context "when calling ##{method}" do
1558
1558
 
1559
- it 'returns the last document, sorted by _id' do
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 'when calling #last' do
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