mongoid 5.0.0 → 5.0.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/CHANGELOG.md +54 -2
  5. data/lib/config/locales/en.yml +1 -1
  6. data/lib/mongoid/attributes.rb +1 -1
  7. data/lib/mongoid/clients.rb +7 -4
  8. data/lib/mongoid/clients/options.rb +2 -2
  9. data/lib/mongoid/contextual/aggregable/mongo.rb +2 -1
  10. data/lib/mongoid/contextual/geo_near.rb +1 -1
  11. data/lib/mongoid/contextual/memory.rb +4 -1
  12. data/lib/mongoid/contextual/mongo.rb +4 -5
  13. data/lib/mongoid/document.rb +1 -0
  14. data/lib/mongoid/indexable/specification.rb +3 -5
  15. data/lib/mongoid/indexable/validators/options.rb +7 -1
  16. data/lib/mongoid/matchable/exists.rb +1 -1
  17. data/lib/mongoid/persistable.rb +2 -1
  18. data/lib/mongoid/persistable/creatable.rb +1 -1
  19. data/lib/mongoid/persistable/deletable.rb +1 -1
  20. data/lib/mongoid/persistable/updatable.rb +2 -2
  21. data/lib/mongoid/positional.rb +75 -0
  22. data/lib/mongoid/relations/counter_cache.rb +19 -0
  23. data/lib/mongoid/relations/eager/base.rb +4 -2
  24. data/lib/mongoid/relations/embedded/batchable.rb +10 -3
  25. data/lib/mongoid/relations/proxy.rb +1 -1
  26. data/lib/mongoid/relations/touchable.rb +1 -1
  27. data/lib/mongoid/scopable.rb +6 -5
  28. data/lib/mongoid/selectable.rb +36 -1
  29. data/lib/mongoid/threaded.rb +34 -2
  30. data/lib/mongoid/timestamps/created.rb +1 -2
  31. data/lib/mongoid/timestamps/timeless.rb +19 -2
  32. data/lib/mongoid/timestamps/updated.rb +1 -1
  33. data/lib/mongoid/traversable.rb +1 -1
  34. data/lib/mongoid/version.rb +1 -1
  35. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +3 -1
  36. data/spec/app/models/account.rb +8 -0
  37. data/spec/app/models/answer.rb +2 -0
  38. data/spec/app/models/article.rb +2 -0
  39. data/spec/app/models/author.rb +2 -0
  40. data/spec/app/models/baby.rb +4 -0
  41. data/spec/app/models/book.rb +2 -0
  42. data/spec/app/models/consumption_period.rb +7 -0
  43. data/spec/app/models/exhibitor.rb +1 -0
  44. data/spec/app/models/kaleidoscope.rb +6 -0
  45. data/spec/app/models/kangaroo.rb +4 -0
  46. data/spec/app/models/note.rb +3 -0
  47. data/spec/app/models/page.rb +11 -0
  48. data/spec/app/models/simple.rb +5 -0
  49. data/spec/config/mongoid.yml +3 -1
  50. data/spec/mongoid/atomic/paths_spec.rb +17 -10
  51. data/spec/mongoid/attributes_spec.rb +2 -2
  52. data/spec/mongoid/clients/options_spec.rb +15 -0
  53. data/spec/mongoid/clients_spec.rb +6 -2
  54. data/spec/mongoid/config_spec.rb +3 -2
  55. data/spec/mongoid/contextual/aggregable/mongo_spec.rb +25 -2
  56. data/spec/mongoid/contextual/atomic_spec.rb +6 -6
  57. data/spec/mongoid/contextual/mongo_spec.rb +28 -75
  58. data/spec/mongoid/criteria_spec.rb +54 -0
  59. data/spec/mongoid/fields/standard_spec.rb +1 -1
  60. data/spec/mongoid/fields_spec.rb +1 -1
  61. data/spec/mongoid/indexable/specification_spec.rb +1 -1
  62. data/spec/mongoid/indexable_spec.rb +7 -7
  63. data/spec/mongoid/interceptable_spec.rb +55 -0
  64. data/spec/mongoid/persistable/creatable_spec.rb +19 -0
  65. data/spec/mongoid/persistable/destroyable_spec.rb +50 -0
  66. data/spec/mongoid/persistable/incrementable_spec.rb +56 -4
  67. data/spec/mongoid/persistable/pushable_spec.rb +11 -0
  68. data/spec/mongoid/persistable/savable_spec.rb +20 -2
  69. data/spec/mongoid/positional_spec.rb +221 -0
  70. data/spec/mongoid/query_cache_spec.rb +19 -0
  71. data/spec/mongoid/relations/auto_save_spec.rb +1 -1
  72. data/spec/mongoid/relations/bindings/referenced/many_to_many_spec.rb +1 -1
  73. data/spec/mongoid/relations/counter_cache_spec.rb +64 -11
  74. data/spec/mongoid/relations/eager/has_many_spec.rb +37 -0
  75. data/spec/mongoid/relations/eager_spec.rb +11 -0
  76. data/spec/mongoid/relations/embedded/many_spec.rb +38 -9
  77. data/spec/mongoid/relations/embedded/one_spec.rb +1 -1
  78. data/spec/mongoid/relations/proxy_spec.rb +22 -0
  79. data/spec/mongoid/relations/reflections_spec.rb +1 -1
  80. data/spec/mongoid/scopable_spec.rb +160 -19
  81. data/spec/mongoid/selectable_spec.rb +16 -6
  82. data/spec/mongoid/timestamps/timeless_spec.rb +17 -0
  83. data/spec/mongoid/validatable/uniqueness_spec.rb +17 -0
  84. metadata +40 -5
  85. metadata.gz.sig +3 -0
@@ -84,7 +84,7 @@ module Mongoid
84
84
  target
85
85
  end
86
86
 
87
- # Tell the next persistance operation to store in a specific collection,
87
+ # Tell the next persistence operation to store in a specific collection,
88
88
  # database or client.
89
89
  #
90
90
  # @example Save the current document to a different collection.
@@ -31,7 +31,7 @@ module Mongoid
31
31
  touches = touch_atomic_updates(field)
32
32
  unless touches.empty?
33
33
  selector = atomic_selector
34
- _root.collection.find(selector).update_one(touches)
34
+ _root.collection.find(selector).update_one(positionally(selector, touches))
35
35
  end
36
36
  run_callbacks(:touch)
37
37
  true
@@ -115,7 +115,7 @@ module Mongoid
115
115
  #
116
116
  # @since 3.0.0
117
117
  def queryable
118
- Threaded.current_scope || Criteria.new(self)
118
+ Threaded.current_scope(self) || Criteria.new(self)
119
119
  end
120
120
 
121
121
  # Create a scope that can be accessed from the class level or chained to
@@ -222,11 +222,11 @@ module Mongoid
222
222
  #
223
223
  # @since 1.0.0
224
224
  def with_scope(criteria)
225
- Threaded.current_scope = criteria
225
+ Threaded.set_current_scope(criteria, self)
226
226
  begin
227
227
  yield criteria
228
228
  ensure
229
- Threaded.current_scope = nil
229
+ Threaded.set_current_scope(nil, self)
230
230
  end
231
231
  end
232
232
 
@@ -311,11 +311,12 @@ module Mongoid
311
311
  # @since 3.0.0
312
312
  def define_scope_method(name)
313
313
  singleton_class.class_eval do
314
- define_method name do |*args|
314
+ define_method(name) do |*args|
315
315
  scoping = _declared_scopes[name]
316
316
  scope = instance_exec(*args, &scoping[:scope])
317
317
  extension = scoping[:extension]
318
- criteria = with_default_scope.merge(scope || queryable)
318
+ to_merge = scope || queryable
319
+ criteria = to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge)
319
320
  criteria.extend(extension)
320
321
  criteria
321
322
  end
@@ -18,7 +18,42 @@ module Mongoid
18
18
  #
19
19
  # @since 1.0.0
20
20
  def atomic_selector
21
- @atomic_selector ||= { "_id" => _root._id }.merge!(shard_key_selector)
21
+ @atomic_selector ||=
22
+ (embedded? ? embedded_atomic_selector : root_atomic_selector)
23
+ end
24
+
25
+ private
26
+
27
+ # Get the atomic selector for an embedded document.
28
+ #
29
+ # @api private
30
+ #
31
+ # @example Get the embedded atomic selector.
32
+ # document.embedded_atomic_selector
33
+ #
34
+ # @return [ Hash ] The embedded document selector.
35
+ #
36
+ # @since 4.0.0
37
+ def embedded_atomic_selector
38
+ if persisted? && _id_changed?
39
+ _parent.atomic_selector
40
+ else
41
+ _parent.atomic_selector.merge("#{atomic_path}._id" => _id)
42
+ end
43
+ end
44
+
45
+ # Get the atomic selector for a root document.
46
+ #
47
+ # @api private
48
+ #
49
+ # @example Get the root atomic selector.
50
+ # document.root_atomic_selector
51
+ #
52
+ # @return [ Hash ] The root document selector.
53
+ #
54
+ # @since 4.0.0
55
+ def root_atomic_selector
56
+ { "_id" => _id }.merge!(shard_key_selector)
22
57
  end
23
58
  end
24
59
  end
@@ -197,13 +197,22 @@ module Mongoid
197
197
  # Get the current Mongoid scope.
198
198
  #
199
199
  # @example Get the scope.
200
+ # Threaded.current_scope(klass)
200
201
  # Threaded.current_scope
201
202
  #
203
+ # @param [ Klass ] klass The class type of the scope.
204
+ #
202
205
  # @return [ Criteria ] The scope.
203
206
  #
204
207
  # @since 5.0.0
205
- def current_scope
206
- Thread.current[CURRENT_SCOPE_KEY]
208
+ def current_scope(klass = nil)
209
+ if klass && Thread.current[CURRENT_SCOPE_KEY].respond_to?(:keys)
210
+ Thread.current[CURRENT_SCOPE_KEY][
211
+ Thread.current[CURRENT_SCOPE_KEY].keys.find { |k| k <= klass }
212
+ ]
213
+ else
214
+ Thread.current[CURRENT_SCOPE_KEY]
215
+ end
207
216
  end
208
217
 
209
218
  # Set the current Mongoid scope.
@@ -220,6 +229,29 @@ module Mongoid
220
229
  Thread.current[CURRENT_SCOPE_KEY] = scope
221
230
  end
222
231
 
232
+ # Set the current Mongoid scope. Safe for multi-model scope chaining.
233
+ #
234
+ # @example Set the scope.
235
+ # Threaded.current_scope(scope, klass)
236
+ #
237
+ # @param [ Criteria ] scope The current scope.
238
+ # @param [ Class ] klass The current model class.
239
+ #
240
+ # @return [ Criteria ] The scope.
241
+ #
242
+ # @since 5.0.1
243
+ def set_current_scope(scope, klass)
244
+ if scope.nil?
245
+ if Thread.current[CURRENT_SCOPE_KEY]
246
+ Thread.current[CURRENT_SCOPE_KEY].delete(klass)
247
+ Thread.current[CURRENT_SCOPE_KEY] = nil if Thread.current[CURRENT_SCOPE_KEY].empty?
248
+ end
249
+ else
250
+ Thread.current[CURRENT_SCOPE_KEY] ||= {}
251
+ Thread.current[CURRENT_SCOPE_KEY][klass] = scope
252
+ end
253
+ end
254
+
223
255
  # Is the document autosaved on the current thread?
224
256
  #
225
257
  # @example Is the document autosaved?
@@ -26,8 +26,7 @@ module Mongoid
26
26
  self.updated_at = time if is_a?(Updated) && !updated_at_changed?
27
27
  self.created_at = time
28
28
  end
29
-
30
- self.class.clear_timeless_option
29
+ clear_timeless_option
31
30
  end
32
31
  end
33
32
  end
@@ -16,7 +16,12 @@ module Mongoid
16
16
  #
17
17
  # @since 3.1.4
18
18
  def clear_timeless_option
19
- self.class.clear_timeless_option
19
+ if self.persisted?
20
+ self.class.clear_timeless_option_on_update
21
+ else
22
+ self.class.clear_timeless_option
23
+ end
24
+ true
20
25
  end
21
26
 
22
27
  # Begin an execution that should skip timestamping.
@@ -67,11 +72,23 @@ module Mongoid
67
72
  def clear_timeless_option
68
73
  if counter = Timeless[name]
69
74
  counter -= 1
70
- Timeless[name] = (counter == 0) ? nil : counter
75
+ set_timeless_counter(counter)
71
76
  end
72
77
  true
73
78
  end
74
79
 
80
+ def clear_timeless_option_on_update
81
+ if counter = Timeless[name]
82
+ counter -= 1 if self < Mongoid::Timestamps::Created
83
+ counter -= 1 if self < Mongoid::Timestamps::Updated
84
+ set_timeless_counter(counter)
85
+ end
86
+ end
87
+
88
+ def set_timeless_counter(counter)
89
+ Timeless[name] = (counter == 0) ? nil : counter
90
+ end
91
+
75
92
  def timeless?
76
93
  !!Timeless[name]
77
94
  end
@@ -26,7 +26,7 @@ module Mongoid
26
26
  self.updated_at = Time.now.utc unless updated_at_changed?
27
27
  end
28
28
 
29
- self.class.clear_timeless_option
29
+ clear_timeless_option
30
30
  end
31
31
 
32
32
  # Is the updated timestamp able to be set?
@@ -153,7 +153,7 @@ module Mongoid
153
153
  def _root
154
154
  object = self
155
155
  while (object._parent) do object = object._parent; end
156
- object || self
156
+ object.with(@persistence_options) || self
157
157
  end
158
158
 
159
159
  # Is this document the root document of the hierarchy?
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "5.0.0"
3
+ VERSION = "5.0.1"
4
4
  end
@@ -20,6 +20,8 @@ development:
20
20
  # (default: primary)
21
21
  # read:
22
22
  # mode: :secondary_preferred
23
+ # tag_sets:
24
+ # - use: web
23
25
 
24
26
  # The name of the user for authentication.
25
27
  # user: 'user'
@@ -131,5 +133,5 @@ test:
131
133
  - localhost:27017
132
134
  options:
133
135
  read:
134
- mode: primary
136
+ mode: :primary
135
137
  max_pool_size: 1
@@ -25,4 +25,12 @@ class Account
25
25
  def overridden
26
26
  self[:overridden] = "not recommended"
27
27
  end
28
+
29
+ # MONGOID-3365
30
+ field :period_started_at, type: Time
31
+ has_many :consumption_periods, dependent: :destroy, validate: false
32
+
33
+ def current_consumption
34
+ consumption_periods.find_or_create_by(started_at: period_started_at)
35
+ end
28
36
  end
@@ -1,4 +1,6 @@
1
1
  class Answer
2
2
  include Mongoid::Document
3
3
  embedded_in :question
4
+
5
+ field :position, type: Integer
4
6
  end
@@ -1,6 +1,8 @@
1
1
  class Article
2
2
  include Mongoid::Document
3
3
 
4
+ field :author_id, type: Integer
5
+ field :public, type: Mongoid::Boolean
4
6
  field :title, type: String
5
7
  field :is_rss, type: Mongoid::Boolean, default: false
6
8
  field :user_login, type: String
@@ -1,4 +1,6 @@
1
1
  class Author
2
2
  include Mongoid::Document
3
+ field :id, type: Integer
4
+ field :author, type: Mongoid::Boolean
3
5
  field :name, type: String
4
6
  end
@@ -0,0 +1,4 @@
1
+ class Baby
2
+ include Mongoid::Document
3
+ embedded_in :kangaroo
4
+ end
@@ -10,4 +10,6 @@ class Book
10
10
  after_initialize do |doc|
11
11
  doc.chapters = 5
12
12
  end
13
+
14
+ embeds_many :pages
13
15
  end
@@ -0,0 +1,7 @@
1
+ class ConsumptionPeriod
2
+ include Mongoid::Document
3
+
4
+ belongs_to :account
5
+
6
+ field :started_at, type: Time
7
+ end
@@ -1,5 +1,6 @@
1
1
  class Exhibitor
2
2
  include Mongoid::Document
3
+ field :status, type: String
3
4
  belongs_to :exhibition
4
5
  has_and_belongs_to_many :artworks
5
6
  end
@@ -0,0 +1,6 @@
1
+ class Kaleidoscope
2
+ include Mongoid::Document
3
+ field :active, type: Mongoid::Boolean, default: true
4
+
5
+ scope :activated, -> { where(active: true) }
6
+ end
@@ -0,0 +1,4 @@
1
+ class Kangaroo
2
+ include Mongoid::Document
3
+ embeds_one :baby
4
+ end
@@ -11,4 +11,7 @@ class Note
11
11
  def update_saved
12
12
  self.saved = true
13
13
  end
14
+
15
+ embedded_in :page
16
+ field :message, :type => String
14
17
  end
@@ -2,4 +2,15 @@ class Page
2
2
  include Mongoid::Document
3
3
  embedded_in :quiz
4
4
  embeds_many :page_questions
5
+
6
+ embedded_in :book
7
+ embeds_many :notes
8
+ field :content, :type => String
9
+
10
+ after_initialize do
11
+ if self[:content]
12
+ self[:text] = self[:content]
13
+ self.remove_attribute(:content)
14
+ end
15
+ end
5
16
  end
@@ -0,0 +1,5 @@
1
+ class Simple
2
+ include Mongoid::Document
3
+ field :name, type: String
4
+ scope :nothing, -> { none }
5
+ end
@@ -9,7 +9,9 @@ test:
9
9
  password: "password"
10
10
  auth_source: "admin"
11
11
  read:
12
- mode: :primary
12
+ mode: :primary_preferred
13
+ tag_sets:
14
+ - use: web
13
15
  max_pool_size: 1
14
16
  options:
15
17
  include_root_in_json: false
@@ -18,7 +18,7 @@ describe Mongoid::Atomic::Paths do
18
18
  Name.new
19
19
  end
20
20
 
21
- describe "#.atomic_delete_modifier" do
21
+ describe "#atomic_delete_modifier" do
22
22
 
23
23
  before do
24
24
  person.addresses << address
@@ -40,7 +40,7 @@ describe Mongoid::Atomic::Paths do
40
40
  end
41
41
  end
42
42
 
43
- describe "#.atomic_insert_modifier" do
43
+ describe "#atomic_insert_modifier" do
44
44
 
45
45
  before do
46
46
  person.addresses << address
@@ -62,7 +62,7 @@ describe Mongoid::Atomic::Paths do
62
62
  end
63
63
  end
64
64
 
65
- describe "#.atomic_path" do
65
+ describe "#atomic_path" do
66
66
 
67
67
  context "when the document is a parent" do
68
68
 
@@ -95,7 +95,7 @@ describe Mongoid::Atomic::Paths do
95
95
  end
96
96
  end
97
97
 
98
- describe "#.atomic_selector" do
98
+ describe "#atomic_selector" do
99
99
 
100
100
  context "when the document is a parent" do
101
101
 
@@ -111,7 +111,9 @@ describe Mongoid::Atomic::Paths do
111
111
  end
112
112
 
113
113
  it "returns the association with id.atomic_selector" do
114
- expect(address.atomic_selector).to eq({ "_id" => person.id })
114
+ expect(address.atomic_selector).to eq(
115
+ { "_id" => person.id, "addresses._id" => address.id }
116
+ )
115
117
  end
116
118
  end
117
119
 
@@ -122,13 +124,19 @@ describe Mongoid::Atomic::Paths do
122
124
  person.addresses << address
123
125
  end
124
126
 
125
- it "returns the JSON notation to the document with id" do
126
- expect(location.atomic_selector).to eq({ "_id" => person.id })
127
+ it "returns the JSON notation to the document with ids" do
128
+ expect(location.atomic_selector).to eq(
129
+ {
130
+ "_id" => person.id,
131
+ "addresses._id" => address.id,
132
+ "addresses.locations._id" => location.id
133
+ }
134
+ )
127
135
  end
128
136
  end
129
137
  end
130
138
 
131
- describe "#.atomic_position" do
139
+ describe "#atomic_position" do
132
140
 
133
141
  context "when the document is a parent" do
134
142
 
@@ -194,7 +202,7 @@ describe Mongoid::Atomic::Paths do
194
202
  end
195
203
  end
196
204
 
197
- describe "#.atomic_path" do
205
+ describe "#atomic_path" do
198
206
 
199
207
  context "when the document is a parent" do
200
208
 
@@ -255,7 +263,6 @@ describe Mongoid::Atomic::Paths do
255
263
  it "returns the.atomic_path plus index" do
256
264
  expect(location.atomic_path).to eq("addresses.0.locations")
257
265
  end
258
-
259
266
  end
260
267
  end
261
268
  end