mongoid 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/lib/mongoid/changeable.rb +1 -1
  7. data/lib/mongoid/clients.rb +1 -0
  8. data/lib/mongoid/clients/options.rb +119 -7
  9. data/lib/mongoid/config.rb +7 -0
  10. data/lib/mongoid/config/options.rb +15 -0
  11. data/lib/mongoid/contextual/geo_near.rb +12 -0
  12. data/lib/mongoid/contextual/mongo.rb +6 -0
  13. data/lib/mongoid/criteria.rb +2 -56
  14. data/lib/mongoid/criteria/findable.rb +4 -1
  15. data/lib/mongoid/criteria/includable.rb +142 -0
  16. data/lib/mongoid/criteria/modifiable.rb +13 -1
  17. data/lib/mongoid/document.rb +21 -0
  18. data/lib/mongoid/fields/foreign_key.rb +5 -1
  19. data/lib/mongoid/fields/localized.rb +16 -1
  20. data/lib/mongoid/fields/validators/macro.rb +1 -0
  21. data/lib/mongoid/findable.rb +1 -0
  22. data/lib/mongoid/loggable.rb +1 -1
  23. data/lib/mongoid/matchable.rb +6 -1
  24. data/lib/mongoid/persistable.rb +2 -2
  25. data/lib/mongoid/relations/eager.rb +12 -5
  26. data/lib/mongoid/relations/eager/base.rb +3 -1
  27. data/lib/mongoid/relations/referenced/many.rb +39 -6
  28. data/lib/mongoid/relations/targets/enumerable.rb +3 -3
  29. data/lib/mongoid/scopable.rb +5 -2
  30. data/lib/mongoid/version.rb +1 -1
  31. data/spec/app/models/address.rb +2 -0
  32. data/spec/app/models/agent.rb +2 -0
  33. data/spec/app/models/alert.rb +2 -0
  34. data/spec/app/models/post.rb +1 -0
  35. data/spec/config/mongoid.yml +1 -0
  36. data/spec/mongoid/changeable_spec.rb +1 -1
  37. data/spec/mongoid/clients/options_spec.rb +57 -0
  38. data/spec/mongoid/config_spec.rb +38 -0
  39. data/spec/mongoid/contextual/geo_near_spec.rb +19 -0
  40. data/spec/mongoid/criteria/findable_spec.rb +11 -0
  41. data/spec/mongoid/criteria/modifiable_spec.rb +126 -0
  42. data/spec/mongoid/criteria_spec.rb +81 -5
  43. data/spec/mongoid/document_spec.rb +56 -0
  44. data/spec/mongoid/fields/foreign_key_spec.rb +23 -0
  45. data/spec/mongoid/fields/localized_spec.rb +32 -14
  46. data/spec/mongoid/matchable_spec.rb +127 -1
  47. data/spec/mongoid/persistable_spec.rb +24 -0
  48. data/spec/mongoid/relations/embedded/many_spec.rb +16 -0
  49. data/spec/mongoid/relations/referenced/many_spec.rb +60 -0
  50. data/spec/mongoid/relations/referenced/many_to_many_spec.rb +40 -0
  51. data/spec/mongoid/scopable_spec.rb +67 -0
  52. data/spec/spec_helper.rb +4 -0
  53. data/spec/support/authorization.rb +2 -1
  54. metadata +6 -5
  55. metadata.gz.sig +0 -0
@@ -55,6 +55,18 @@ module Mongoid
55
55
  create_document(:create!, attrs, &block)
56
56
  end
57
57
 
58
+ # Define attributes with which new documents will be created.
59
+ #
60
+ # @example Define attributes to be used when a new document is created.
61
+ # Person.create_with(job: 'Engineer').find_or_create_by(employer: 'MongoDB')
62
+ #
63
+ # @return [ Mongoid::Criteria ] A criteria.
64
+ #
65
+ # @since 5.1.0
66
+ def create_with(attrs = {})
67
+ where(selector.merge(attrs))
68
+ end
69
+
58
70
  # Find the first +Document+ given the conditions, or creates a new document
59
71
  # with the conditions that were supplied.
60
72
  #
@@ -160,7 +172,7 @@ module Mongoid
160
172
  #
161
173
  # @since 3.0.0
162
174
  def create_document(method, attrs = nil, &block)
163
- attributes = selector.reduce(attrs || {}) do |hash, (key, value)|
175
+ attributes = selector.reduce(attrs ? attrs.dup : {}) do |hash, (key, value)|
164
176
  unless key.to_s =~ /\$/ || value.is_a?(Hash)
165
177
  hash[key] = value
166
178
  end
@@ -180,6 +180,27 @@ module Mongoid
180
180
  attributes
181
181
  end
182
182
 
183
+ # Calls #as_json on the document with additional, Mongoid-specific options.
184
+ #
185
+ # @example Get the document as json.
186
+ # document.as_json(compact: true)
187
+ #
188
+ # @param [ Hash ] options The options.
189
+ #
190
+ # @option options [ true, false ] :compact Whether to include fields with
191
+ # nil values in the json document.
192
+ #
193
+ # @return [ Hash ] The document as json.
194
+ #
195
+ # @since 5.1.0
196
+ def as_json(options = nil)
197
+ if options && (options[:compact] == true)
198
+ super(options).reject! { |k,v| v.nil? }
199
+ else
200
+ super(options)
201
+ end
202
+ end
203
+
183
204
  # Returns an instance of the specified class with the attributes,
184
205
  # errors, and embedded documents of the current document.
185
206
  #
@@ -63,7 +63,11 @@ module Mongoid
63
63
  # @since 3.0.0
64
64
  def evolve(object)
65
65
  if object_id_field? || object.is_a?(Document)
66
- object.__evolve_object_id__
66
+ if metadata.polymorphic? && constraint
67
+ constraint.convert(object)
68
+ else
69
+ object.__evolve_object_id__
70
+ end
67
71
  else
68
72
  related_id_field.evolve(object)
69
73
  end
@@ -48,6 +48,21 @@ module Mongoid
48
48
 
49
49
  private
50
50
 
51
+ # Are fallbacks being used for this localized field.
52
+ #
53
+ # @api private
54
+ #
55
+ # @example Should fallbacks be used.
56
+ # field.fallbacks?
57
+ #
58
+ # @return [ true, false ] If fallbacks should be used.
59
+ #
60
+ # @since 5.1.0
61
+ def fallbacks?
62
+ return true if options[:fallbacks].nil?
63
+ !!options[:fallbacks]
64
+ end
65
+
51
66
  # Lookup the value from the provided object.
52
67
  #
53
68
  # @api private
@@ -62,7 +77,7 @@ module Mongoid
62
77
  # @since 3.0.0
63
78
  def lookup(object)
64
79
  locale = ::I18n.locale
65
- if ::I18n.respond_to?(:fallbacks)
80
+ if fallbacks? && ::I18n.respond_to?(:fallbacks)
66
81
  object[::I18n.fallbacks[locale].map(&:to_s).find{ |loc| object.has_key?(loc) }]
67
82
  else
68
83
  object[locale.to_s]
@@ -13,6 +13,7 @@ module Mongoid
13
13
  :identity,
14
14
  :label,
15
15
  :localize,
16
+ :fallbacks,
16
17
  :metadata,
17
18
  :pre_processed,
18
19
  :subtype,
@@ -15,6 +15,7 @@ module Mongoid
15
15
  delegate \
16
16
  :aggregates,
17
17
  :avg,
18
+ :create_with,
18
19
  :distinct,
19
20
  :each,
20
21
  :each_with_index,
@@ -48,7 +48,7 @@ module Mongoid
48
48
  # @since 3.0.0
49
49
  def default_logger
50
50
  logger = Logger.new($stdout)
51
- logger.level = Logger::INFO
51
+ logger.level = Mongoid::Config.log_level
52
52
  logger
53
53
  end
54
54
 
@@ -55,7 +55,12 @@ module Mongoid
55
55
  selector.each_pair do |key, value|
56
56
  if value.is_a?(Hash)
57
57
  value.each do |item|
58
- return false unless matcher(self, key, Hash[*item]).matches?(Hash[*item])
58
+ if item[0].to_s == "$not".freeze
59
+ item = item[1]
60
+ return false if matcher(self, key, item).matches?(item)
61
+ else
62
+ return false unless matcher(self, key, Hash[*item]).matches?(Hash[*item])
63
+ end
59
64
  end
60
65
  else
61
66
  return false unless matcher(self, key, value).matches?(value)
@@ -55,7 +55,7 @@ module Mongoid
55
55
  # @since 4.0.0
56
56
  def atomically
57
57
  begin
58
- @atomic_updates_to_execute = {}
58
+ @atomic_updates_to_execute = @atomic_updates_to_execute || {}
59
59
  yield(self) if block_given?
60
60
  persist_atomic_operations(@atomic_updates_to_execute)
61
61
  true
@@ -207,7 +207,7 @@ module Mongoid
207
207
  #
208
208
  # @since 4.0.0
209
209
  def persist_atomic_operations(operations)
210
- if persisted?
210
+ if persisted? && operations
211
211
  selector = atomic_selector
212
212
  _root.collection.find(selector).update_one(positionally(selector, operations))
213
213
  end
@@ -34,11 +34,18 @@ module Mongoid
34
34
  end
35
35
 
36
36
  def preload(relations, docs)
37
-
38
- relations.group_by do |metadata|
39
- metadata.relation
40
- end.each do |relation, associations|
41
- relation.eager_load_klass.new(associations, docs).run
37
+ grouped_relations = relations.group_by do |metadata|
38
+ metadata.inverse_class_name
39
+ end
40
+ grouped_relations.keys.each do |_klass|
41
+ grouped_relations[_klass] = grouped_relations[_klass].group_by do |metadata|
42
+ metadata.relation
43
+ end
44
+ end
45
+ grouped_relations.each do |_klass, associations|
46
+ docs = associations.collect do |_relation, association|
47
+ _relation.eager_load_klass.new(association, docs).run
48
+ end.flatten
42
49
  end
43
50
  end
44
51
  end
@@ -45,10 +45,12 @@ module Mongoid
45
45
  #
46
46
  # @since 4.0.0
47
47
  def run
48
+ @loaded = []
48
49
  while shift_metadata
49
50
  preload
51
+ @loaded << @docs.collect { |d| d.send(@metadata.name) }
50
52
  end
51
- @docs
53
+ @loaded.flatten
52
54
  end
53
55
 
54
56
  # Preload the current relation.
@@ -320,14 +320,47 @@ module Mongoid
320
320
  #
321
321
  # @since 2.0.0.rc.1
322
322
  def append(document)
323
- # @todo: remove?
324
323
  document.with(@persistence_options) if @persistence_options
324
+ with_add_callbacks(document, already_related?(document)) do
325
+ target.push(document)
326
+ characterize_one(document)
327
+ bind_one(document)
328
+ end
329
+ end
325
330
 
326
- execute_callback :before_add, document
327
- target.push(document)
328
- characterize_one(document)
329
- bind_one(document)
330
- execute_callback :after_add, document
331
+ # Execute before/after add callbacks around the block unless the objects
332
+ # already have a persisted relation.
333
+ #
334
+ # @example Execute before/after add callbacks around the block.
335
+ # relation.with_add_callbacks(document, false)
336
+ #
337
+ # @param [ Document ] document The document to append to the target.
338
+ # @param [ true, false ] already_related Whether the document is already related
339
+ # to the target.
340
+ #
341
+ # @since 5.1.0
342
+ def with_add_callbacks(document, already_related)
343
+ execute_callback :before_add, document unless already_related
344
+ yield
345
+ execute_callback :after_add, document unless already_related
346
+ end
347
+
348
+ # Whether the document and the base already have a persisted relation.
349
+ #
350
+ # @example Is the document already related to the base.
351
+ # relation.already_related?(document)
352
+ #
353
+ # @param [ Document ] document The document to possibly append to the target.
354
+ #
355
+ # @return [ true, false ] Whether the document is already related to the base and the
356
+ # relation is persisted.
357
+ #
358
+ # @since 5.1.0
359
+ def already_related?(document)
360
+ document.persisted? &&
361
+ document.__metadata &&
362
+ document.respond_to?(document.__metadata.foreign_key) &&
363
+ document.__send__(document.__metadata.foreign_key) == base.id
331
364
  end
332
365
 
333
366
  # Instantiate the binding associated with this relation.
@@ -241,7 +241,7 @@ module Mongoid
241
241
  else
242
242
  @_added, @executed = {}, true
243
243
  @_loaded = target.inject({}) do |_target, doc|
244
- _target[doc._id] = doc
244
+ _target[doc._id] = doc if doc
245
245
  _target
246
246
  end
247
247
  end
@@ -340,7 +340,7 @@ module Mongoid
340
340
  #
341
341
  # @since 3.0.15
342
342
  def marshal_dump
343
- [ _added, _loaded, _unloaded ]
343
+ [ _added, _loaded, _unloaded, @executed]
344
344
  end
345
345
 
346
346
  # Loads the data needed to Marshal.load an enumerable proxy.
@@ -352,7 +352,7 @@ module Mongoid
352
352
  #
353
353
  # @since 3.0.15
354
354
  def marshal_load(data)
355
- @_added, @_loaded, @_unloaded = data
355
+ @_added, @_loaded, @_unloaded, @executed = data
356
356
  end
357
357
 
358
358
  # Reset the enumerable back to its persisted state.
@@ -87,7 +87,8 @@ module Mongoid
87
87
  # @return [ Proc ] The default scope.
88
88
  #
89
89
  # @since 1.0.0
90
- def default_scope(value)
90
+ def default_scope(value = nil)
91
+ value = Proc.new { yield } if block_given?
91
92
  check_scope_validity(value)
92
93
  self.default_scoping = process_default_scope(value)
93
94
  end
@@ -115,7 +116,9 @@ module Mongoid
115
116
  #
116
117
  # @since 3.0.0
117
118
  def queryable
118
- Threaded.current_scope(self) || Criteria.new(self)
119
+ crit = Threaded.current_scope(self) || Criteria.new(self)
120
+ crit.embedded = true if crit.klass.embedded?
121
+ crit
119
122
  end
120
123
 
121
124
  # Create a scope that can be accessed from the class level or chained to
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "5.0.2"
3
+ VERSION = "5.1.0"
4
4
  end
@@ -44,6 +44,8 @@ class Address
44
44
  belongs_to :band
45
45
 
46
46
  scope :without_postcode, ->{ where(postcode: nil) }
47
+ scope :ordered, ->{ order_by(state: 1) }
48
+ scope :without_postcode_ordered, ->{ without_postcode.ordered }
47
49
  scope :rodeo, ->{ where(street: "Rodeo Dr") } do
48
50
  def mansion?
49
51
  all? { |address| address.street == "Rodeo Dr" }
@@ -5,8 +5,10 @@ class Agent
5
5
  field :number, type: String
6
6
  field :dob, type: Time
7
7
  embeds_many :names, as: :namable
8
+ embeds_one :address
8
9
  belongs_to :game
9
10
  belongs_to :agency, touch: true, autobuild: true
10
11
 
11
12
  has_and_belongs_to_many :accounts
13
+ has_and_belongs_to_many :basics
12
14
  end
@@ -2,4 +2,6 @@ class Alert
2
2
  include Mongoid::Document
3
3
  field :message, type: String
4
4
  belongs_to :account
5
+ has_many :items
6
+ belongs_to :post
5
7
  end
@@ -14,6 +14,7 @@ class Post
14
14
  has_and_belongs_to_many :tags, before_add: :before_add_tag, after_add: :after_add_tag, before_remove: :before_remove_tag, after_remove: :after_remove_tag
15
15
  has_many :videos, validate: false
16
16
  has_many :roles, validate: false
17
+ has_many :alerts
17
18
 
18
19
  belongs_to :posteable, polymorphic: true
19
20
  accepts_nested_attributes_for :posteable, autosave: true
@@ -21,3 +21,4 @@ test:
21
21
  raise_not_found_error: true
22
22
  use_activesupport_time_zone: true
23
23
  use_utc: false
24
+ log_level: :warn
@@ -940,7 +940,7 @@ describe Mongoid::Changeable do
940
940
  end
941
941
 
942
942
  it "returns a hash with indifferent access" do
943
- expect(person.changes["title"]).to eq(
943
+ expect(person.changes[:title]).to eq(
944
944
  [ nil, "Captain Obvious" ]
945
945
  )
946
946
  end
@@ -28,6 +28,41 @@ describe Mongoid::Clients::Options do
28
28
  expect(Band.new.persistence_options).to be_nil
29
29
  end
30
30
 
31
+ context 'when passed a block', if: testing_locally? do
32
+
33
+ let!(:connections_before) do
34
+ Band.mongo_client.database.command(serverStatus: 1).first['connections']['current']
35
+ end
36
+
37
+ before do
38
+ Band.with(options) do |klass|
39
+ klass.where(name: 'emily').to_a
40
+ end
41
+ end
42
+
43
+ let(:connections_after) do
44
+ Band.mongo_client.database.command(serverStatus: 1).first['connections']['current']
45
+ end
46
+
47
+ context 'when a new cluster is created by the driver' do
48
+
49
+ let(:options) { { connect_timeout: 2 } }
50
+
51
+ it 'disconnects the new cluster' do
52
+ expect(connections_after).to eq(connections_before)
53
+ end
54
+ end
55
+
56
+ context 'when the same cluster is used by the new client' do
57
+
58
+ let(:options) { { database: 'same-cluster' } }
59
+
60
+ it 'does not disconnect the original cluster' do
61
+ expect(connections_after).to eq(connections_before)
62
+ end
63
+ end
64
+ end
65
+
31
66
  context "when calling .collection method" do
32
67
 
33
68
  before do
@@ -82,6 +117,28 @@ describe Mongoid::Clients::Options do
82
117
  it "passes down the options to collection" do
83
118
  expect(instance.collection.database.name).to eq('test')
84
119
  end
120
+
121
+ context "when the object is shared between threads" do
122
+
123
+ before do
124
+ threads = []
125
+ doc = Band.create(name: "Beatles")
126
+ 100.times do |i|
127
+ threads << Thread.new do
128
+ if i % 2 == 0
129
+ doc.with(nil).set(name: "Rolling Stones")
130
+ else
131
+ doc.with(options).set(name: "Beatles")
132
+ end
133
+ end
134
+ end
135
+ threads.join
136
+ end
137
+
138
+ it "does not share the persistence options" do
139
+ expect(Band.persistence_options).to eq(nil)
140
+ end
141
+ end
85
142
  end
86
143
 
87
144
  describe "#persistence_options" do
@@ -60,6 +60,23 @@ describe Mongoid::Config do
60
60
  end
61
61
  end
62
62
 
63
+ context "when the log level is not set in the configuration" do
64
+
65
+ before do
66
+ Mongoid.configure do |config|
67
+ config.load_configuration(CONFIG)
68
+ end
69
+ end
70
+
71
+ it "sets the Mongoid logger level to the default" do
72
+ expect(Mongoid.logger.level).to eq(Logger::INFO)
73
+ end
74
+
75
+ it "sets the Mongo driver logger level to the default" do
76
+ expect(Mongo::Logger.logger.level).to eq(Logger::INFO)
77
+ end
78
+ end
79
+
63
80
  describe "#load!" do
64
81
 
65
82
  before(:all) do
@@ -89,6 +106,27 @@ describe Mongoid::Config do
89
106
  end
90
107
  end
91
108
 
109
+ context "when the log level is set in the configuration" do
110
+
111
+ before do
112
+ described_class.load!(file, :test)
113
+ end
114
+
115
+ after do
116
+ Mongoid.configure do |config|
117
+ config.load_configuration(CONFIG)
118
+ end
119
+ end
120
+
121
+ it "sets the Mongoid logger level" do
122
+ expect(Mongoid.logger.level).to eq(Logger::WARN)
123
+ end
124
+
125
+ it "sets the Mongo driver logger level" do
126
+ expect(Mongo::Logger.logger.level).to eq(Logger::WARN)
127
+ end
128
+ end
129
+
92
130
  context "when provided an environment" do
93
131
 
94
132
  before do