mongoid 3.0.0.rc → 3.0.0

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