mongoid 5.0.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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