mongoid 3.0.23 → 3.1.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 (95) hide show
  1. data/CHANGELOG.md +253 -9
  2. data/LICENSE +1 -1
  3. data/README.md +4 -1
  4. data/lib/config/locales/en.yml +7 -6
  5. data/lib/mongoid.rb +18 -1
  6. data/lib/mongoid/atomic.rb +22 -20
  7. data/lib/mongoid/atomic/paths/embedded.rb +19 -5
  8. data/lib/mongoid/atomic/paths/root.rb +1 -1
  9. data/lib/mongoid/atomic/positionable.rb +73 -0
  10. data/lib/mongoid/attributes.rb +63 -1
  11. data/lib/mongoid/callbacks.rb +58 -4
  12. data/lib/mongoid/components.rb +8 -3
  13. data/lib/mongoid/config.rb +71 -23
  14. data/lib/mongoid/contextual.rb +2 -1
  15. data/lib/mongoid/contextual/aggregable/mongo.rb +27 -63
  16. data/lib/mongoid/contextual/atomic.rb +4 -3
  17. data/lib/mongoid/contextual/find_and_modify.rb +1 -1
  18. data/lib/mongoid/contextual/geo_near.rb +238 -0
  19. data/lib/mongoid/contextual/map_reduce.rb +12 -1
  20. data/lib/mongoid/contextual/memory.rb +36 -31
  21. data/lib/mongoid/contextual/mongo.rb +147 -91
  22. data/lib/mongoid/contextual/queryable.rb +25 -0
  23. data/lib/mongoid/copyable.rb +4 -1
  24. data/lib/mongoid/criteria.rb +23 -275
  25. data/lib/mongoid/criterion/findable.rb +179 -0
  26. data/lib/mongoid/criterion/modifiable.rb +191 -0
  27. data/lib/mongoid/criterion/scoping.rb +11 -6
  28. data/lib/mongoid/document.rb +7 -56
  29. data/lib/mongoid/equality.rb +66 -0
  30. data/lib/mongoid/errors/mongoid_error.rb +7 -3
  31. data/lib/mongoid/extensions/array.rb +13 -1
  32. data/lib/mongoid/extensions/date.rb +9 -2
  33. data/lib/mongoid/extensions/hash.rb +38 -2
  34. data/lib/mongoid/extensions/nil_class.rb +12 -0
  35. data/lib/mongoid/extensions/object.rb +24 -0
  36. data/lib/mongoid/extensions/string.rb +14 -2
  37. data/lib/mongoid/extensions/time.rb +4 -1
  38. data/lib/mongoid/fields.rb +49 -5
  39. data/lib/mongoid/fields/foreign_key.rb +12 -0
  40. data/lib/mongoid/fields/standard.rb +12 -0
  41. data/lib/mongoid/finders.rb +8 -0
  42. data/lib/mongoid/hierarchy.rb +19 -1
  43. data/lib/mongoid/indexes.rb +30 -4
  44. data/lib/mongoid/indexes/validators/options.rb +12 -2
  45. data/lib/mongoid/inspection.rb +2 -1
  46. data/lib/mongoid/matchers/strategies.rb +5 -5
  47. data/lib/mongoid/observer.rb +27 -36
  48. data/lib/mongoid/persistence.rb +42 -17
  49. data/lib/mongoid/persistence/atomic.rb +10 -5
  50. data/lib/mongoid/persistence/atomic/operation.rb +26 -9
  51. data/lib/mongoid/persistence/atomic/unset.rb +1 -1
  52. data/lib/mongoid/persistence/operations/embedded/insert.rb +5 -2
  53. data/lib/mongoid/persistence/operations/embedded/remove.rb +5 -2
  54. data/lib/mongoid/persistence/operations/update.rb +7 -3
  55. data/lib/mongoid/railties/database.rake +12 -19
  56. data/lib/mongoid/relations.rb +2 -0
  57. data/lib/mongoid/relations/accessors.rb +30 -8
  58. data/lib/mongoid/relations/binding.rb +5 -1
  59. data/lib/mongoid/relations/bindings/referenced/in.rb +1 -1
  60. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +3 -3
  61. data/lib/mongoid/relations/counter_cache.rb +107 -0
  62. data/lib/mongoid/relations/embedded/batchable.rb +13 -4
  63. data/lib/mongoid/relations/embedded/many.rb +30 -1
  64. data/lib/mongoid/relations/macros.rb +2 -0
  65. data/lib/mongoid/relations/marshalable.rb +0 -1
  66. data/lib/mongoid/relations/metadata.rb +63 -11
  67. data/lib/mongoid/relations/options.rb +1 -0
  68. data/lib/mongoid/relations/proxy.rb +45 -2
  69. data/lib/mongoid/relations/referenced/in.rb +11 -2
  70. data/lib/mongoid/relations/referenced/many.rb +31 -3
  71. data/lib/mongoid/relations/referenced/many_to_many.rb +31 -3
  72. data/lib/mongoid/relations/referenced/one.rb +1 -1
  73. data/lib/mongoid/relations/targets/enumerable.rb +5 -1
  74. data/lib/mongoid/relations/touchable.rb +35 -6
  75. data/lib/mongoid/reloading.rb +5 -3
  76. data/lib/mongoid/scoping.rb +2 -2
  77. data/lib/mongoid/sessions.rb +57 -7
  78. data/lib/mongoid/sessions/factory.rb +22 -1
  79. data/lib/mongoid/threaded.rb +4 -30
  80. data/lib/mongoid/threaded/lifecycle.rb +12 -12
  81. data/lib/mongoid/timestamps.rb +1 -0
  82. data/lib/mongoid/timestamps/created.rb +2 -0
  83. data/lib/mongoid/timestamps/created/short.rb +19 -0
  84. data/lib/mongoid/timestamps/short.rb +10 -0
  85. data/lib/mongoid/timestamps/updated.rb +2 -0
  86. data/lib/mongoid/timestamps/updated/short.rb +19 -0
  87. data/lib/mongoid/validations.rb +2 -0
  88. data/lib/mongoid/validations/queryable.rb +2 -2
  89. data/lib/mongoid/validations/uniqueness.rb +1 -18
  90. data/lib/mongoid/version.rb +1 -1
  91. data/lib/rails/generators/mongoid/model/model_generator.rb +1 -0
  92. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +3 -0
  93. data/lib/rails/mongoid.rb +53 -29
  94. data/lib/support/ruby_version.rb +26 -0
  95. metadata +18 -7
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+
4
+ # This module contains the behaviour of Mongoid's clone/dup of documents.
5
+ module Equality
6
+
7
+ # Default comparison is via the string version of the id.
8
+ #
9
+ # @example Compare two documents.
10
+ # person <=> other_person
11
+ #
12
+ # @param [ Document ] other The document to compare with.
13
+ #
14
+ # @return [ Integer ] -1, 0, 1.
15
+ #
16
+ # @since 1.0.0
17
+ def <=>(other)
18
+ attributes["_id"].to_s <=> other.attributes["_id"].to_s
19
+ end
20
+
21
+ # Performs equality checking on the document ids. For more robust
22
+ # equality checking please override this method.
23
+ #
24
+ # @example Compare for equality.
25
+ # document == other
26
+ #
27
+ # @param [ Document, Object ] other The other object to compare with.
28
+ #
29
+ # @return [ true, false ] True if the ids are equal, false if not.
30
+ #
31
+ # @since 1.0.0
32
+ def ==(other)
33
+ self.class == other.class &&
34
+ attributes["_id"] == other.attributes["_id"]
35
+ end
36
+
37
+ # Performs class equality checking.
38
+ #
39
+ # @example Compare the classes.
40
+ # document === other
41
+ #
42
+ # @param [ Document, Object ] other The other object to compare with.
43
+ #
44
+ # @return [ true, false ] True if the classes are equal, false if not.
45
+ #
46
+ # @since 1.0.0
47
+ def ===(other)
48
+ other.class == Class ? self.class === other : self == other
49
+ end
50
+
51
+ # Delegates to ==. Used when needing checks in hashes.
52
+ #
53
+ # @example Perform equality checking.
54
+ # document.eql?(other)
55
+ #
56
+ # @param [ Document, Object ] other The object to check against.
57
+ #
58
+ # @return [ true, false ] True if equal, false if not.
59
+ #
60
+ # @since 1.0.0
61
+ def eql?(other)
62
+ self == (other)
63
+ end
64
+ end
65
+ end
66
+
@@ -18,9 +18,13 @@ module Mongoid
18
18
  #
19
19
  # @since 3.0.0
20
20
  def compose_message(key, attributes)
21
- "\nProblem:\n #{problem(key, attributes)}"+
22
- "\nSummary:\n #{summary(key, attributes)}"+
23
- "\nResolution:\n #{resolution(key, attributes)}"
21
+ @problem = problem(key, attributes)
22
+ @summary = summary(key, attributes)
23
+ @resolution = resolution(key, attributes)
24
+
25
+ "\nProblem:\n #{@problem}"+
26
+ "\nSummary:\n #{@summary}"+
27
+ "\nResolution:\n #{@resolution}"
24
28
  end
25
29
 
26
30
  private
@@ -53,6 +53,18 @@ module Mongoid
53
53
  ::Time.configured.local(*self)
54
54
  end
55
55
 
56
+ # Check if the array is part of a blank relation criteria.
57
+ #
58
+ # @example Is the array blank criteria?
59
+ # [].blank_criteria?
60
+ #
61
+ # @return [ true, false ] If the array is blank criteria.
62
+ #
63
+ # @since 3.1.0
64
+ def blank_criteria?
65
+ any?(&:blank_criteria?)
66
+ end
67
+
56
68
  # Is the array a set of multiple arguments in a method?
57
69
  #
58
70
  # @example Is this multi args?
@@ -62,7 +74,7 @@ module Mongoid
62
74
  #
63
75
  # @since 3.0.0
64
76
  def multi_arged?
65
- first.resizable? || size > 1
77
+ !first.is_a?(Hash) && first.resizable? || size > 1
66
78
  end
67
79
 
68
80
  # Turn the object from the ruby type we deal with to a Mongo friendly
@@ -3,6 +3,9 @@ module Mongoid
3
3
  module Extensions
4
4
  module Date
5
5
 
6
+ # Constant for epoch - used when passing invalid times.
7
+ EPOCH = ::Date.new(1970, 1, 1)
8
+
6
9
  # Convert the date into a time.
7
10
  #
8
11
  # @example Convert the date to a time.
@@ -57,8 +60,12 @@ module Mongoid
57
60
  # @since 3.0.0
58
61
  def mongoize(object)
59
62
  unless object.blank?
60
- time = object.__mongoize_time__
61
- ::Time.utc(time.year, time.month, time.day)
63
+ begin
64
+ time = object.__mongoize_time__
65
+ ::Time.utc(time.year, time.month, time.day)
66
+ rescue ArgumentError
67
+ EPOCH
68
+ end
62
69
  end
63
70
  end
64
71
  end
@@ -35,18 +35,33 @@ module Mongoid
35
35
  # @return [ Hash ] A new consolidated hash.
36
36
  #
37
37
  # @since 3.0.0
38
- def __consolidate__
38
+ def __consolidate__(klass)
39
39
  consolidated = {}
40
40
  each_pair do |key, value|
41
41
  if key =~ /\$/
42
+ value.each_pair do |_key, _value|
43
+ value[_key] = mongoize_for(klass, _key, _value)
44
+ end
42
45
  (consolidated[key] ||= {}).merge!(value)
43
46
  else
44
- (consolidated["$set"] ||= {}).merge!(key => value)
47
+ (consolidated["$set"] ||= {}).merge!(key => mongoize_for(klass, key, value))
45
48
  end
46
49
  end
47
50
  consolidated
48
51
  end
49
52
 
53
+ # Check if the hash is part of a blank relation criteria.
54
+ #
55
+ # @example Is the hash blank criteria?
56
+ # {}.blank_criteria?
57
+ #
58
+ # @return [ true, false ] If the hash is blank criteria.
59
+ #
60
+ # @since 3.1.0
61
+ def blank_criteria?
62
+ self == { "_id" => { "$in" => [] }}
63
+ end
64
+
50
65
  # Deletes an id value from the hash.
51
66
  #
52
67
  # @example Delete an id value.
@@ -135,6 +150,27 @@ module Mongoid
135
150
  criteria
136
151
  end
137
152
 
153
+ private
154
+
155
+ # Mongoize for the klass, key and value.
156
+ #
157
+ # @api private
158
+ #
159
+ # @example Mongoize for the klass, field and value.
160
+ # {}.mongoize_for(Band, "name", "test")
161
+ #
162
+ # @param [ Class ] klass The model class.
163
+ # @param [ String, Symbol ] The field key.
164
+ # @param [ Object ] value The value to mongoize.
165
+ #
166
+ # @return [ Object ] The mongoized value.
167
+ #
168
+ # @since 3.1.0
169
+ def mongoize_for(klass, key, value)
170
+ field = klass.fields[key.to_s]
171
+ field ? field.mongoize(value) : value
172
+ end
173
+
138
174
  module ClassMethods
139
175
 
140
176
  # Turn the object from the ruby type we deal with to a Mongo friendly
@@ -3,6 +3,18 @@ module Mongoid
3
3
  module Extensions
4
4
  module NilClass
5
5
 
6
+ # Try to form a setter from this object.
7
+ #
8
+ # @example Try to form a setter.
9
+ # object.__setter__
10
+ #
11
+ # @return [ nil ] Always nil.
12
+ #
13
+ # @since 3.1.0
14
+ def __setter__
15
+ self
16
+ end
17
+
6
18
  # Get the name of a nil collection.
7
19
  #
8
20
  # @example Get the nil name.
@@ -40,6 +40,18 @@ module Mongoid
40
40
  self
41
41
  end
42
42
 
43
+ # Try to form a setter from this object.
44
+ #
45
+ # @example Try to form a setter.
46
+ # object.__setter__
47
+ #
48
+ # @return [ String ] The object as a string plus =.
49
+ #
50
+ # @since 3.1.0
51
+ def __setter__
52
+ "#{self}="
53
+ end
54
+
43
55
  # Get the value of the object as a mongo friendy sort value.
44
56
  #
45
57
  # @example Get the object as sort criteria.
@@ -64,6 +76,18 @@ module Mongoid
64
76
  self
65
77
  end
66
78
 
79
+ # Check if the object is part of a blank relation criteria.
80
+ #
81
+ # @example Is the object blank criteria?
82
+ # "".blank_criteria?
83
+ #
84
+ # @return [ true, false ] If the object is blank criteria.
85
+ #
86
+ # @since 3.1.0
87
+ def blank_criteria?
88
+ false
89
+ end
90
+
67
91
  # Do or do not, there is no try. -- Yoda.
68
92
  #
69
93
  # @example Do or do not.
@@ -81,7 +81,7 @@ module Mongoid
81
81
  #
82
82
  # @since 3.0.0
83
83
  def numeric?
84
- true if Float(self) rescue (self == "NaN")
84
+ true if Float(self) rescue false
85
85
  end
86
86
 
87
87
  # Get the string as a getter string.
@@ -93,7 +93,7 @@ module Mongoid
93
93
  #
94
94
  # @since 1.0.0
95
95
  def reader
96
- delete("=")
96
+ delete("=").sub(/\_before\_type\_cast$/, '')
97
97
  end
98
98
 
99
99
  # Convert the string to an array with the string in it.
@@ -132,6 +132,18 @@ module Mongoid
132
132
  /[@$"]/ !~ to_sym.inspect
133
133
  end
134
134
 
135
+ # Does the string end with _before_type_cast?
136
+ #
137
+ # @example Is the string a setter method?
138
+ # "price_before_type_cast".before_type_cast?
139
+ #
140
+ # @return [ true, false ] If the string ends with "_before_type_cast"
141
+ #
142
+ # @since 3.1.0
143
+ def before_type_cast?
144
+ ends_with?("_before_type_cast")
145
+ end
146
+
135
147
  # Is the object not to be converted to bson on criteria creation?
136
148
  #
137
149
  # @example Is the object unconvertable?
@@ -3,6 +3,9 @@ module Mongoid
3
3
  module Extensions
4
4
  module Time
5
5
 
6
+ # Constant for epoch - used when passing invalid times.
7
+ EPOCH = ::Time.utc(1970, 1, 1, 0, 0, 0)
8
+
6
9
  # Turn the object from the ruby type we deal with to a Mongo friendly
7
10
  # type.
8
11
  #
@@ -73,7 +76,7 @@ module Mongoid
73
76
  ::Time.at(time.to_i, time.usec).utc
74
77
  end
75
78
  rescue ArgumentError
76
- raise Errors::InvalidTime.new(object)
79
+ EPOCH
77
80
  end
78
81
  end
79
82
  end
@@ -23,7 +23,6 @@ module Mongoid
23
23
  self.pre_processed_defaults = []
24
24
  self.post_processed_defaults = []
25
25
 
26
- field(:_type, default: ->{ self.class.name if hereditary? }, type: String)
27
26
  field(
28
27
  :_id,
29
28
  default: ->{ Moped::BSON::ObjectId.new },
@@ -77,7 +76,7 @@ module Mongoid
77
76
  unless attributes.has_key?(name)
78
77
  if field = fields[name]
79
78
  default = field.eval_default(self)
80
- unless default.nil?
79
+ unless default.nil? || field.lazy?
81
80
  attribute_will_change!(name)
82
81
  attributes[name] = default
83
82
  end
@@ -126,6 +125,21 @@ module Mongoid
126
125
  self.class.database_field_name(name)
127
126
  end
128
127
 
128
+ # Is the provided field a lazy evaluation?
129
+ #
130
+ # @example If the field is lazy settable.
131
+ # doc.lazy_settable?(field, nil)
132
+ #
133
+ # @param [ Field ] field The field.
134
+ # @param [ Object ] value The current value.
135
+ #
136
+ # @return [ true, false ] If we set the field lazily.
137
+ #
138
+ # @since 3.1.0
139
+ def lazy_settable?(field, value)
140
+ !frozen? && value.nil? && field.lazy?
141
+ end
142
+
129
143
  # Is the document using object ids?
130
144
  #
131
145
  # @note Refactored from using delegate for class load performance.
@@ -336,6 +350,7 @@ module Mongoid
336
350
  # person.name #=> returns the field
337
351
  # person.name = "" #=> sets the field
338
352
  # person.name? #=> Is the field present?
353
+ # person.name_before_type_cast #=> returns the field before type cast
339
354
  #
340
355
  # @param [ Symbol ] name The name of the field.
341
356
  # @param [ Symbol ] meth The name of the accessor.
@@ -346,6 +361,7 @@ module Mongoid
346
361
  field = fields[name]
347
362
 
348
363
  create_field_getter(name, meth, field)
364
+ create_field_getter_before_type_cast(name, meth)
349
365
  create_field_setter(name, meth, field)
350
366
  create_field_check(name, meth)
351
367
 
@@ -369,9 +385,37 @@ module Mongoid
369
385
  def create_field_getter(name, meth, field)
370
386
  generated_methods.module_eval do
371
387
  re_define_method(meth) do
372
- value = fields[name].demongoize(read_attribute(name))
373
- attribute_will_change!(name) if value.resizable?
374
- value
388
+ raw = read_attribute(name)
389
+ if lazy_settable?(field, raw)
390
+ write_attribute(name, field.eval_default(self))
391
+ else
392
+ value = field.demongoize(raw)
393
+ attribute_will_change!(name) if value.resizable?
394
+ value
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ # Create the getter_before_type_cast method for the provided field. If
401
+ # the attribute has been assigned, return the attribute before it was
402
+ # type cast. Otherwise, delegate to the getter.
403
+ #
404
+ # @example Create the getter_before_type_cast.
405
+ # Model.create_field_getter_before_type_cast("name", "name")
406
+ #
407
+ # @param [ String ] name The name of the attribute.
408
+ # @param [ String ] meth The name of the method.
409
+ #
410
+ # @since 3.1.0
411
+ def create_field_getter_before_type_cast(name, meth)
412
+ generated_methods.module_eval do
413
+ re_define_method("#{meth}_before_type_cast") do
414
+ if has_attribute_before_type_cast?(name)
415
+ read_attribute_before_type_cast(name)
416
+ else
417
+ send meth
418
+ end
375
419
  end
376
420
  end
377
421
  end
@@ -69,6 +69,18 @@ module Mongoid
69
69
  end
70
70
  end
71
71
 
72
+ # Does this field do lazy default evaluation?
73
+ #
74
+ # @example Is the field lazy?
75
+ # field.lazy?
76
+ #
77
+ # @return [ true, false ] If the field is lazy.
78
+ #
79
+ # @since 3.1.0
80
+ def lazy?
81
+ type.resizable?
82
+ end
83
+
72
84
  # Mongoize the object into the Mongo friendly value.
73
85
  #
74
86
  # @example Mongoize the object.
@@ -95,6 +95,18 @@ module Mongoid
95
95
  end
96
96
  end
97
97
 
98
+ # Does this field do lazy default evaluation?
99
+ #
100
+ # @example Is the field lazy?
101
+ # field.lazy?
102
+ #
103
+ # @return [ true, false ] If the field is lazy.
104
+ #
105
+ # @since 3.1.0
106
+ def lazy?
107
+ false
108
+ end
109
+
98
110
  # Is the field localized or not?
99
111
  #
100
112
  # @example Is the field localized?