mongoid 3.0.0.rc → 3.0.0

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 (82) hide show
  1. data/CHANGELOG.md +109 -4
  2. data/README.md +1 -1
  3. data/Rakefile +1 -0
  4. data/lib/config/locales/en.yml +15 -1
  5. data/lib/mongoid.rb +17 -2
  6. data/lib/mongoid/atomic.rb +54 -7
  7. data/lib/mongoid/attributes.rb +1 -1
  8. data/lib/mongoid/attributes/processing.rb +1 -1
  9. data/lib/mongoid/callbacks.rb +6 -1
  10. data/lib/mongoid/components.rb +2 -1
  11. data/lib/mongoid/config.rb +42 -17
  12. data/lib/mongoid/config/environment.rb +3 -1
  13. data/lib/mongoid/contextual/aggregable/memory.rb +21 -10
  14. data/lib/mongoid/contextual/find_and_modify.rb +14 -12
  15. data/lib/mongoid/contextual/memory.rb +24 -1
  16. data/lib/mongoid/contextual/mongo.rb +148 -29
  17. data/lib/mongoid/copyable.rb +6 -24
  18. data/lib/mongoid/criteria.rb +116 -34
  19. data/lib/mongoid/document.rb +7 -7
  20. data/lib/mongoid/errors.rb +1 -0
  21. data/lib/mongoid/errors/no_metadata.rb +21 -0
  22. data/lib/mongoid/evolvable.rb +19 -0
  23. data/lib/mongoid/extensions.rb +1 -1
  24. data/lib/mongoid/extensions/array.rb +38 -1
  25. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  26. data/lib/mongoid/extensions/date_time.rb +6 -1
  27. data/lib/mongoid/extensions/false_class.rb +12 -0
  28. data/lib/mongoid/extensions/float.rb +12 -0
  29. data/lib/mongoid/extensions/hash.rb +33 -1
  30. data/lib/mongoid/extensions/integer.rb +12 -0
  31. data/lib/mongoid/extensions/object.rb +51 -1
  32. data/lib/mongoid/extensions/object_id.rb +2 -1
  33. data/lib/mongoid/extensions/range.rb +24 -0
  34. data/lib/mongoid/extensions/string.rb +31 -5
  35. data/lib/mongoid/extensions/true_class.rb +12 -0
  36. data/lib/mongoid/fields.rb +20 -21
  37. data/lib/mongoid/fields/foreign_key.rb +23 -7
  38. data/lib/mongoid/fields/standard.rb +3 -3
  39. data/lib/mongoid/finders.rb +3 -7
  40. data/lib/mongoid/hierarchy.rb +19 -1
  41. data/lib/mongoid/identity_map.rb +20 -4
  42. data/lib/mongoid/indexes/validators/options.rb +1 -1
  43. data/lib/mongoid/multi_parameter_attributes.rb +1 -1
  44. data/lib/mongoid/paranoia.rb +3 -32
  45. data/lib/mongoid/persistence.rb +33 -15
  46. data/lib/mongoid/persistence/atomic/operation.rb +1 -1
  47. data/lib/mongoid/persistence/operations.rb +16 -0
  48. data/lib/mongoid/persistence/operations/remove.rb +1 -1
  49. data/lib/mongoid/persistence/operations/upsert.rb +28 -0
  50. data/lib/mongoid/persistence/upsertion.rb +30 -0
  51. data/lib/mongoid/relations.rb +16 -0
  52. data/lib/mongoid/relations/accessors.rb +1 -1
  53. data/lib/mongoid/relations/bindings/referenced/in.rb +1 -1
  54. data/lib/mongoid/relations/builder.rb +1 -1
  55. data/lib/mongoid/relations/builders/nested_attributes/one.rb +1 -1
  56. data/lib/mongoid/relations/builders/referenced/many.rb +1 -1
  57. data/lib/mongoid/relations/cascading.rb +4 -3
  58. data/lib/mongoid/relations/constraint.rb +1 -1
  59. data/lib/mongoid/relations/conversions.rb +1 -1
  60. data/lib/mongoid/relations/embedded/batchable.rb +3 -2
  61. data/lib/mongoid/relations/embedded/many.rb +4 -4
  62. data/lib/mongoid/relations/embedded/one.rb +1 -1
  63. data/lib/mongoid/relations/metadata.rb +67 -23
  64. data/lib/mongoid/relations/nested_builder.rb +2 -2
  65. data/lib/mongoid/relations/proxy.rb +9 -7
  66. data/lib/mongoid/relations/referenced/many.rb +69 -25
  67. data/lib/mongoid/relations/referenced/many_to_many.rb +14 -13
  68. data/lib/mongoid/scoping.rb +0 -17
  69. data/lib/mongoid/serialization.rb +95 -26
  70. data/lib/mongoid/sessions.rb +30 -6
  71. data/lib/mongoid/sessions/factory.rb +2 -0
  72. data/lib/mongoid/threaded.rb +52 -0
  73. data/lib/mongoid/timestamps/created.rb +1 -1
  74. data/lib/mongoid/timestamps/updated.rb +2 -1
  75. data/lib/mongoid/validations/uniqueness.rb +3 -2
  76. data/lib/mongoid/version.rb +1 -1
  77. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +8 -0
  78. data/lib/rails/mongoid.rb +8 -5
  79. metadata +30 -13
  80. data/lib/mongoid/collections/retry.rb +0 -58
  81. data/lib/mongoid/javascript.rb +0 -20
  82. data/lib/mongoid/javascript/functions.yml +0 -63
@@ -8,11 +8,13 @@ module Mongoid
8
8
 
9
9
  # Get the name of the environment that we are running under. This first
10
10
  # looks for Rails, then Sinatra, then a RACK_ENV environment variable,
11
- # and if none of those are found returns "development".
11
+ # and if none of those are found raises an error.
12
12
  #
13
13
  # @example Get the env name.
14
14
  # Environment.env_name
15
15
  #
16
+ # @raise [ Errors::NoEnvironment ] If no environment was set.
17
+ #
16
18
  # @return [ String ] The name of the current environment.
17
19
  #
18
20
  # @since 2.3.0
@@ -38,11 +38,7 @@ module Mongoid
38
38
  #
39
39
  # @since 3.0.0
40
40
  def max(field = nil)
41
- if block_given?
42
- super()
43
- else
44
- count > 0 ? max_by { |doc| doc.send(field) }.send(field) : nil
45
- end
41
+ block_given? ? super() : aggregate_by(field, :max_by)
46
42
  end
47
43
 
48
44
  # Get the min value of the provided field. If provided a block, will
@@ -64,11 +60,7 @@ module Mongoid
64
60
  #
65
61
  # @since 3.0.0
66
62
  def min(field = nil)
67
- if block_given?
68
- super()
69
- else
70
- count > 0 ? min_by { |doc| doc.send(field) }.send(field) : nil
71
- end
63
+ block_given? ? super() : aggregate_by(field, :min_by)
72
64
  end
73
65
 
74
66
  # Get the sum value of the provided field. If provided a block, will
@@ -92,6 +84,25 @@ module Mongoid
92
84
  count > 0 ? super(0) { |doc| doc.send(field) } : nil
93
85
  end
94
86
  end
87
+
88
+ private
89
+
90
+ # Aggregate by the provided field and method.
91
+ #
92
+ # @api private
93
+ #
94
+ # @example Aggregate by the field and method.
95
+ # aggregable.aggregate_by(:name, :min_by)
96
+ #
97
+ # @param [ Symbol ] field The field to aggregate on.
98
+ # @param [ Symbol ] method The method (min_by or max_by).
99
+ #
100
+ # @return [ Integer ] The aggregate.
101
+ #
102
+ # @since 3.0.0
103
+ def aggregate_by(field, method)
104
+ count > 0 ? send(method) { |doc| doc.send(field) }.send(field) : nil
105
+ end
95
106
  end
96
107
  end
97
108
  end
@@ -4,6 +4,12 @@ module Mongoid
4
4
  class FindAndModify
5
5
  include Command
6
6
 
7
+ # @attribute [r] criteria The criteria for the context.
8
+ # @attribute [r] options The command options.
9
+ # @attribute [r] update The updates.
10
+ # @attribute [r] query The Moped query.
11
+ attr_reader :criteria, :options, :update, :query
12
+
7
13
  # Initialize the find and modify command, used for MongoDB's
8
14
  # $findAndModify.
9
15
  #
@@ -16,13 +22,12 @@ module Mongoid
16
22
  #
17
23
  # @option options [ true, false ] :new Return the updated document.
18
24
  # @option options [ true, false ] :remove Delete the first document.
25
+ # @option options [ true, false ] :upsert Create the document if it doesn't exist.
19
26
  #
20
27
  # @since 3.0.0
21
28
  def initialize(criteria, update, options = {})
22
- @criteria = criteria
23
- command[:findandmodify] = criteria.klass.collection_name.to_s
24
- command[:update] = update unless options[:remove]
25
- command.merge!(options)
29
+ @criteria, @options, @update = criteria, options, update
30
+ @query = criteria.klass.collection.find(criteria.selector)
26
31
  apply_criteria_options
27
32
  end
28
33
 
@@ -35,9 +40,7 @@ module Mongoid
35
40
  #
36
41
  # @since 3.0.0
37
42
  def result
38
- session.with(consistency: :strong) do |session|
39
- session.command(command)["value"]
40
- end
43
+ query.modify(update, options)
41
44
  end
42
45
 
43
46
  private
@@ -53,12 +56,11 @@ module Mongoid
53
56
  #
54
57
  # @since 3.0.0
55
58
  def apply_criteria_options
56
- command[:query] = criteria.selector
57
- if sort = criteria.options[:sort]
58
- command[:sort] = sort
59
+ if spec = criteria.options[:sort]
60
+ query.sort(spec)
59
61
  end
60
- if fields = criteria.options[:fields]
61
- command[:fields] = fields
62
+ if spec = criteria.options[:fields]
63
+ query.select(spec)
62
64
  end
63
65
  end
64
66
  end
@@ -345,6 +345,27 @@ module Mongoid
345
345
  end
346
346
  end
347
347
 
348
+ # Compare two values, checking for nil.
349
+ #
350
+ # @api private
351
+ #
352
+ # @example Compare the two objects.
353
+ # context.compare(a, b)
354
+ #
355
+ # @param [ Object ] a The first object.
356
+ # @param [ Object ] b The first object.
357
+ #
358
+ # @return [ Integer ] The comparison value.
359
+ #
360
+ # @since 3.0.0
361
+ def compare(a, b)
362
+ case
363
+ when a.nil? then b.nil? ? 0 : 1
364
+ when b.nil? then -1
365
+ else a <=> b
366
+ end
367
+ end
368
+
348
369
  # Sort the documents in place.
349
370
  #
350
371
  # @example Sort the documents.
@@ -356,7 +377,9 @@ module Mongoid
356
377
  def in_place_sort(values)
357
378
  values.each_pair do |field, dir|
358
379
  documents.sort! do |a, b|
359
- dir > 0 ? a[field] <=> b[field] : b[field] <=> a[field]
380
+ a_value, b_value = a[field], b[field]
381
+ value = compare(a_value.__sortable__, b_value.__sortable__)
382
+ dir < 0 ? value * -1 : value
360
383
  end
361
384
  end
362
385
  end
@@ -33,6 +33,18 @@ module Mongoid
33
33
  end
34
34
  alias :empty? :blank?
35
35
 
36
+ # Is the context cached?
37
+ #
38
+ # @example Is the context cached?
39
+ # context.cached?
40
+ #
41
+ # @return [ true, false ] If the context is cached.
42
+ #
43
+ # @since 3.0.0
44
+ def cached?
45
+ !!@cache
46
+ end
47
+
36
48
  # Get the number of documents matching the query.
37
49
  #
38
50
  # @example Get the number of matching documents.
@@ -114,25 +126,15 @@ module Mongoid
114
126
  # @return [ Enumerator ] The enumerator.
115
127
  #
116
128
  # @since 3.0.0
117
- def each
129
+ def each(&block)
118
130
  if block_given?
119
131
  reset_length
120
132
  selecting do
121
- if eager_loadable?
122
- docs = query.map{ |doc| Factory.from_db(klass, doc) }
123
- eager_load(docs)
124
- docs.each do |doc|
125
- yield doc
126
- increment_length
127
- end
128
- docs
129
- else
130
- query.each do |doc|
131
- yield Factory.from_db(klass, doc)
132
- increment_length
133
- end
134
- self
133
+ documents_for_iteration.each do |doc|
134
+ yield_and_increment(doc, &block)
135
135
  end
136
+ @cache_loaded = true
137
+ eager_loadable? ? docs : self
136
138
  end
137
139
  else
138
140
  to_enum
@@ -174,6 +176,7 @@ module Mongoid
174
176
  #
175
177
  # @option options [ true, false ] :new Return the updated document.
176
178
  # @option options [ true, false ] :remove Delete the first document.
179
+ # @option options [ true, false ] :upsert Create the document if it doesn't exist.
177
180
  #
178
181
  # @return [ Document ] The result of the command.
179
182
  #
@@ -207,7 +210,7 @@ module Mongoid
207
210
  #
208
211
  # @since 3.0.0
209
212
  def initialize(criteria)
210
- @criteria, @klass = criteria, criteria.klass
213
+ @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
211
214
  add_type_selection
212
215
  @query = klass.collection.find(criteria.selector)
213
216
  apply_options
@@ -304,7 +307,7 @@ module Mongoid
304
307
  # Update all the matching documents atomically.
305
308
  #
306
309
  # @example Update all the matching documents.
307
- # context.update(name: "Smiths")
310
+ # context.update({ "$set" => { name: "Smiths" }})
308
311
  #
309
312
  # @param [ Hash ] attributes The new attributes for each document.
310
313
  #
@@ -313,7 +316,7 @@ module Mongoid
313
316
  # @since 3.0.0
314
317
  def update(attributes = nil)
315
318
  return false unless attributes
316
- query.update_all({ "$set" => attributes })
319
+ query.update_all(attributes.__consolidate__)
317
320
  end
318
321
  alias :update_all :update
319
322
 
@@ -388,6 +391,18 @@ module Mongoid
388
391
  end
389
392
  end
390
393
 
394
+ # Apply the hint option
395
+ #
396
+ # @example Apply the hint params.
397
+ # context.apply_hint
398
+ #
399
+ # @since 3.0.0
400
+ def apply_hint
401
+ if spec = criteria.options[:hint]
402
+ query.hint(spec)
403
+ end
404
+ end
405
+
391
406
  # Map the inverse sort symbols to the correct MongoDB values.
392
407
  #
393
408
  # @example Apply the inverse sorting params.
@@ -402,6 +417,77 @@ module Mongoid
402
417
  end
403
418
  end
404
419
 
420
+ # Is the cache able to be added to?
421
+ #
422
+ # @api private
423
+ #
424
+ # @example Is the context cacheable?
425
+ # context.cacheable?
426
+ #
427
+ # @return [ true, false ] If caching, and the cache isn't loaded.
428
+ #
429
+ # @since 3.0.0
430
+ def cacheable?
431
+ cached? && !cache_loaded?
432
+ end
433
+
434
+ # Is the cache fully loaded? Will be true if caching after one full
435
+ # iteration.
436
+ #
437
+ # @api private
438
+ #
439
+ # @example Is the cache loaded?
440
+ # context.cache_loaded?
441
+ #
442
+ # @return [ true, false ] If the cache is loaded.
443
+ #
444
+ # @since 3.0.0
445
+ def cache_loaded?
446
+ !!@cache_loaded
447
+ end
448
+
449
+ # Get the documents for cached queries.
450
+ #
451
+ # @api private
452
+ #
453
+ # @example Get the cached documents.
454
+ # context.documents
455
+ #
456
+ # @return [ Array<Document> ] The documents.
457
+ #
458
+ # @since 3.0.0
459
+ def documents
460
+ @documents ||= []
461
+ end
462
+
463
+ # Get the documents the context should iterate. This follows 3 rules:
464
+ #
465
+ # 1. If the query is cached, and we already have documents loaded, use
466
+ # them.
467
+ # 2. If we are eager loading, then eager load the documents and use
468
+ # those.
469
+ # 3. Use the query.
470
+ #
471
+ # @api private
472
+ #
473
+ # @example Get the documents for iteration.
474
+ # context.documents_for_iteration
475
+ #
476
+ # @return [ Array<Document>, Moped::Query ] The docs to iterate.
477
+ #
478
+ # @since 3.0.0
479
+ def documents_for_iteration
480
+ if cached? && !documents.empty?
481
+ documents
482
+ elsif eager_loadable?
483
+ docs = query.map{ |doc| Factory.from_db(klass, doc) }
484
+ eager_load(docs)
485
+ docs
486
+ else
487
+ query
488
+ end
489
+ end
490
+
405
491
  # Eager load the inclusions for the provided documents.
406
492
  #
407
493
  # @example Eager load the inclusions.
@@ -414,19 +500,32 @@ module Mongoid
414
500
  # @since 3.0.0
415
501
  def eager_load(docs)
416
502
  criteria.inclusions.reject! do |metadata|
417
- unless docs.empty?
418
- if metadata.stores_foreign_key?
419
- child_ids = load_ids(metadata.foreign_key).flatten
420
- metadata.eager_load(child_ids)
421
- else
422
- parent_ids = docs.map(&:id)
423
- metadata.eager_load(parent_ids)
424
- end
425
- end
503
+ metadata.eager_load(eager_loaded_ids(docs, metadata)) if !docs.empty?
426
504
  end
427
505
  self.eager_loaded = true
428
506
  end
429
507
 
508
+ # Get the ids that to be used to eager load documents.
509
+ #
510
+ # @api private
511
+ #
512
+ # @example Get the ids.
513
+ # context.eager_load(docs, metadata)
514
+ #
515
+ # @param [ Array<Document> ] docs The pre-loaded documents.
516
+ # @param [ Metadata ] metadata The relation metadata.
517
+ #
518
+ # @return [ Array<Object> ] The ids.
519
+ #
520
+ # @since 3.0.0
521
+ def eager_loaded_ids(docs, metadata)
522
+ if metadata.stores_foreign_key?
523
+ load_ids(metadata.foreign_key).flatten
524
+ else
525
+ docs.map(&:id)
526
+ end
527
+ end
528
+
430
529
  # Is this context able to be eager loaded?
431
530
  #
432
531
  # @example Is the context eager loadable?
@@ -436,7 +535,7 @@ module Mongoid
436
535
  #
437
536
  # @since 3.0.0
438
537
  def eager_loadable?
439
- !eager_loaded && criteria.inclusions.any?
538
+ !eager_loaded && !criteria.inclusions.empty?
440
539
  end
441
540
 
442
541
  # Increment the length of the results.
@@ -475,7 +574,7 @@ module Mongoid
475
574
  #
476
575
  # @param [ String ] key The id or foriegn key string.
477
576
  #
478
- # @return [ Array<String, BSON::ObjectId> ] The ids to load.
577
+ # @return [ Array<String, Moped::BSON::ObjectId> ] The ids to load.
479
578
  #
480
579
  # @since 3.0.0
481
580
  def load_ids(key)
@@ -495,6 +594,7 @@ module Mongoid
495
594
  apply_limit
496
595
  apply_skip
497
596
  apply_sorting
597
+ apply_hint
498
598
  end
499
599
 
500
600
  # If we are limiting results, we need to set the field limitations on a
@@ -538,6 +638,25 @@ module Mongoid
538
638
  doc
539
639
  end
540
640
  end
641
+
642
+ # Yield to the document and increment the length.
643
+ #
644
+ # @api private
645
+ #
646
+ # @example Yield and increment.
647
+ # context.yield_and_increment(doc) do |doc|
648
+ # ...
649
+ # end
650
+ #
651
+ # @param [ Document ] document The document to yield to.
652
+ #
653
+ # @since 3.0.0
654
+ def yield_and_increment(document, &block)
655
+ doc = document.respond_to?(:_id) ? document : Factory.from_db(klass, document)
656
+ yield(doc)
657
+ increment_length
658
+ documents.push(doc) if cacheable?
659
+ end
541
660
  end
542
661
  end
543
662
  end
@@ -5,15 +5,6 @@ module Mongoid
5
5
  module Copyable
6
6
  extend ActiveSupport::Concern
7
7
 
8
- COPYABLES = [
9
- :attributes,
10
- :metadata,
11
- :changed_attributes,
12
- :previous_changes
13
- ]
14
-
15
- protected
16
-
17
8
  # Clone or dup the current +Document+. This will return all attributes with
18
9
  # the exception of the document's id and versions, and will reset all the
19
10
  # instance variables.
@@ -23,25 +14,16 @@ module Mongoid
23
14
  # @example Clone the document.
24
15
  # document.clone
25
16
  #
26
- # @example Dup the document.
27
- # document.dup
28
- #
29
17
  # @param [ Document ] other The document getting cloned.
30
18
  #
31
19
  # @return [ Document ] The new document.
32
- def initialize_copy(other)
33
- other.as_document
34
- instance_variables.each { |name| remove_instance_variable(name) }
35
- COPYABLES.each do |name|
36
- value = other.send(name)
37
- instance_variable_set(:"@#{name}", value ? value.dup : nil)
38
- end
39
- attributes.delete("_id")
40
- if attributes.delete("versions")
41
- attributes["version"] = 1
20
+ def clone
21
+ attrs = as_document.except("_id")
22
+ if attrs.delete("versions")
23
+ attrs["version"] = 1
42
24
  end
43
- @new_record = true
44
- apply_defaults
25
+ self.class.new(attrs)
45
26
  end
27
+ alias :dup :clone
46
28
  end
47
29
  end