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
@@ -13,15 +13,23 @@ module Mongoid
13
13
  delegate \
14
14
  :aggregates,
15
15
  :avg,
16
+ :distinct,
16
17
  :each,
18
+ :each_with_index,
17
19
  :extras,
18
20
  :find_and_modify,
19
21
  :find_or_create_by,
20
22
  :find_or_initialize_by,
23
+ :first_or_create,
24
+ :first_or_create!,
25
+ :first_or_initialize,
26
+ :for_js,
27
+ :geo_near,
21
28
  :includes,
22
29
  :map_reduce,
23
30
  :max,
24
31
  :min,
32
+ :pluck,
25
33
  :sum,
26
34
  :update,
27
35
  :update_all, to: :with_default_scope
@@ -131,6 +131,18 @@ module Mongoid
131
131
  object || self
132
132
  end
133
133
 
134
+ # Is this document the root document of the hierarchy?
135
+ #
136
+ # @example Is the document the root?
137
+ # document._root?
138
+ #
139
+ # @return [ true, false ] If the document is the root.
140
+ #
141
+ # @since 3.1.0
142
+ def _root?
143
+ _parent ? false : true
144
+ end
145
+
134
146
  module ClassMethods
135
147
 
136
148
  # Determines if the document is a subclass of another document.
@@ -161,7 +173,13 @@ module Mongoid
161
173
  subclass.pre_processed_defaults = pre_processed_defaults.dup
162
174
  subclass.post_processed_defaults = post_processed_defaults.dup
163
175
  subclass.scopes = scopes.dup
164
- subclass.autosaved_relations = autosaved_relations.dup
176
+
177
+ # We only need the _type field if inheritance is in play, but need to
178
+ # add to the root class as well for backwards compatibility.
179
+ unless fields.has_key?("_type")
180
+ field(:_type, default: self.name, type: String)
181
+ end
182
+ subclass.field(:_type, default: subclass.name, type: String)
165
183
  end
166
184
  end
167
185
  end
@@ -23,7 +23,12 @@ module Mongoid
23
23
  def create_indexes
24
24
  return unless index_options
25
25
  index_options.each_pair do |spec, options|
26
- collection.indexes.create(spec, options)
26
+ if database = options[:database]
27
+ with(consistency: :strong, database: database).
28
+ collection.indexes.create(spec, options.except(:database))
29
+ else
30
+ with(consistency: :strong).collection.indexes.create(spec, options)
31
+ end
27
32
  end and true
28
33
  end
29
34
 
@@ -37,9 +42,13 @@ module Mongoid
37
42
  #
38
43
  # @since 3.0.0
39
44
  def remove_indexes
40
- with(consistency: :strong).collection.indexes.each do |spec|
41
- next if spec["name"] == "_id_"
42
- collection.indexes.drop(spec["key"])
45
+ indexed_database_names.each do |database|
46
+ collection = with(consistency: :strong, database: database).collection
47
+ collection.indexes.each do |spec|
48
+ unless spec["name"] == "_id_"
49
+ collection.indexes.drop(spec["key"])
50
+ end
51
+ end
43
52
  end and true
44
53
  end
45
54
 
@@ -123,6 +132,23 @@ module Mongoid
123
132
  normalized
124
133
  end
125
134
  end
135
+
136
+ # Get the names of all databases for this model that have index
137
+ # definitions.
138
+ #
139
+ # @api private
140
+ #
141
+ # @example Get the indexed database names.
142
+ # Model.indexed_database_names
143
+ #
144
+ # @return [ Array<String> ] The names.
145
+ #
146
+ # @since 3.1.0
147
+ def indexed_database_names
148
+ index_options.values.map do |options|
149
+ options[:database] || database_name
150
+ end.uniq
151
+ end
126
152
  end
127
153
  end
128
154
  end
@@ -9,6 +9,7 @@ module Mongoid
9
9
 
10
10
  VALID_OPTIONS = [
11
11
  :background,
12
+ :database,
12
13
  :drop_dups,
13
14
  :name,
14
15
  :sparse,
@@ -17,10 +18,19 @@ module Mongoid
17
18
  :min,
18
19
  :bits,
19
20
  :bucket_size,
20
- :expire_after_seconds
21
+ :expire_after_seconds,
22
+ :weights
21
23
  ]
22
24
 
23
- VALID_TYPES = [ 1, -1, "2d", "geoHaystack" ]
25
+ VALID_TYPES = [
26
+ 1,
27
+ -1,
28
+ "2d",
29
+ "2dsphere",
30
+ "geoHaystack",
31
+ "text",
32
+ "hashed"
33
+ ]
24
34
 
25
35
  # Validate the index specification.
26
36
  #
@@ -29,7 +29,8 @@ module Mongoid
29
29
  def inspect_fields
30
30
  fields.map do |name, field|
31
31
  unless name == "_id"
32
- "#{name}: #{@attributes[name].inspect}"
32
+ as = field.options[:as]
33
+ "#{name}#{as ? "(#{as})" : nil}: #{@attributes[name].inspect}"
33
34
  end
34
35
  end.compact
35
36
  end
@@ -21,7 +21,7 @@ module Mongoid
21
21
  module Strategies
22
22
  extend self
23
23
 
24
- MATCHERS = HashWithIndifferentAccess.new({
24
+ MATCHERS = {
25
25
  "$all" => Matchers::All,
26
26
  "$and" => Matchers::And,
27
27
  "$exists" => Matchers::Exists,
@@ -34,7 +34,7 @@ module Mongoid
34
34
  "$nin" => Matchers::Nin,
35
35
  "$or" => Matchers::Or,
36
36
  "$size" => Matchers::Size
37
- })
37
+ }.with_indifferent_access
38
38
 
39
39
  # Get the matcher for the supplied key and value. Will determine the class
40
40
  # name from the key.
@@ -58,9 +58,9 @@ module Mongoid
58
58
  Default.new(extract_attribute(document, key))
59
59
  end
60
60
  else
61
- case key
62
- when "$or", :$or then Matchers::Or.new(value, document)
63
- when "$and", :$and then Matchers::And.new(value, document)
61
+ case key.to_s
62
+ when "$or" then Matchers::Or.new(value, document)
63
+ when "$and" then Matchers::And.new(value, document)
64
64
  else Default.new(extract_attribute(document, key))
65
65
  end
66
66
  end
@@ -71,6 +71,9 @@ module Mongoid
71
71
  # * before_update
72
72
  # * around_update
73
73
  # * after_update
74
+ # * before_upsert
75
+ # * around_upsert
76
+ # * after_upsert
74
77
  # * before_save
75
78
  # * around_save
76
79
  # * after_save
@@ -107,29 +110,7 @@ module Mongoid
107
110
  # Observers are singletons and that call instantiates and registers them.
108
111
  class Observer < ActiveModel::Observer
109
112
 
110
- # Instantiate the new observer. Will add all child observers as well.
111
- #
112
- # @example Instantiate the observer.
113
- # Mongoid::Observer.new
114
- #
115
- # @since 2.0.0.rc.8
116
- def initialize
117
- super and observed_descendants.each { |klass| add_observer!(klass) }
118
- end
119
-
120
- protected
121
-
122
- # Get all the child observers.
123
- #
124
- # @example Get the children.
125
- # observer.observed_descendants
126
- #
127
- # @return [ Array<Class> ] The children.
128
- #
129
- # @since 2.0.0.rc.8
130
- def observed_descendants
131
- observed_classes.sum([]) { |klass| klass.descendants }
132
- end
113
+ private
133
114
 
134
115
  # Adds the specified observer to the class.
135
116
  #
@@ -152,25 +133,35 @@ module Mongoid
152
133
  #
153
134
  # @since 2.0.0.rc.8
154
135
  def define_callbacks(klass)
155
- tap do |observer|
156
- observer_name = observer.class.name.underscore.gsub('/', '__')
157
- Mongoid::Callbacks::CALLBACKS.each do |callback|
158
- next unless respond_to?(callback)
159
- callback_meth = :"_notify_#{observer_name}_for_#{callback}"
160
- unless klass.respond_to?(callback_meth)
161
- klass.send(:define_method, callback_meth) do |&block|
162
- if value = observer.update(callback, self, &block)
163
- value
164
- else
165
- block.call if block
166
- end
136
+ observer = self
137
+ observer_name = observer.class.name.underscore.gsub('/', '__')
138
+ Mongoid::Callbacks.observables.each do |callback|
139
+ next unless respond_to?(callback)
140
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
141
+ unless klass.respond_to?(callback_meth)
142
+ klass.send(:define_method, callback_meth) do |&block|
143
+ if value = observer.update(callback, self, &block)
144
+ value
145
+ else
146
+ block.call if block
167
147
  end
168
- klass.send(callback, callback_meth)
169
148
  end
149
+ klass.send(callback, callback_meth)
170
150
  end
171
151
  end
152
+ self
172
153
  end
173
154
 
155
+ # Are the observers disabled for the object?
156
+ #
157
+ # @api private
158
+ #
159
+ # @example If the observer disabled?
160
+ # Observer.disabled_for(band)
161
+ #
162
+ # @param [ Document ] object The model instance.
163
+ #
164
+ # @return [ true, false ] If the observer is disabled.
174
165
  def disabled_for?(object)
175
166
  klass = object.class
176
167
  return false unless klass.respond_to?(:observers)
@@ -19,6 +19,7 @@ module Mongoid
19
19
  module Persistence
20
20
  extend ActiveSupport::Concern
21
21
  include Atomic
22
+ include Mongoid::Atomic::Positionable
22
23
 
23
24
  # Remove the document from the database with callbacks.
24
25
  #
@@ -113,18 +114,22 @@ module Mongoid
113
114
  #
114
115
  # @param [ Symbol ] field The name of an additional field to update.
115
116
  #
116
- # @return [ true ] true.
117
+ # @return [ true/false ] false if record is new_record otherwise true.
117
118
  #
118
119
  # @since 3.0.0
119
120
  def touch(field = nil)
121
+ return false if _root.new_record?
120
122
  current = Time.now
121
123
  write_attribute(:updated_at, current) if fields["updated_at"]
122
124
  write_attribute(field, current) if field
123
- _root.collection.find(atomic_selector).update(touch_atomic_updates(field))
124
- without_autobuild do
125
- touchables.each { |name| send(name).try(:touch) }
125
+
126
+ touches = touch_atomic_updates(field)
127
+ unless touches.empty?
128
+ selector = atomic_selector
129
+ _root.collection.find(selector).update(positionally(selector, touches))
126
130
  end
127
- move_changes and true
131
+ run_callbacks(:touch, :after)
132
+ true
128
133
  end
129
134
 
130
135
  # Update the document in the database.
@@ -220,16 +225,26 @@ module Mongoid
220
225
  # @example Create a new document.
221
226
  # Person.create(:title => "Mr")
222
227
  #
223
- # @param [ Hash ] attributes The attributes to create with.
228
+ # @example Create multiple new documents.
229
+ # Person.create({ title: "Mr" }, { title: "Mrs" })
230
+ #
231
+ # @param [ Hash, Array ] attributes The attributes to create with, or an
232
+ # Array of multiple attributes for multiple documents.
224
233
  # @param [ Hash ] options A mass-assignment protection options. Supports
225
234
  # :as and :without_protection
226
235
  #
227
- # @return [ Document ] The newly created document.
228
- def create(attributes = {}, options = {}, &block)
236
+ # @return [ Document, Array<Document> ] The newly created document(s).
237
+ #
238
+ # @since 1.0.0
239
+ def create(attributes = nil, options = {}, &block)
229
240
  _creating do
230
- doc = new(attributes, options, &block)
231
- doc.save
232
- doc
241
+ if attributes.is_a?(::Array)
242
+ attributes.map { |attrs| create(attrs, options, &block) }
243
+ else
244
+ doc = new(attributes, options, &block)
245
+ doc.save
246
+ doc
247
+ end
233
248
  end
234
249
  end
235
250
 
@@ -241,17 +256,27 @@ module Mongoid
241
256
  # @example Create a new document.
242
257
  # Person.create!(:title => "Mr")
243
258
  #
244
- # @param [ Hash ] attributes The attributes to create with.
259
+ # @example Create multiple new documents.
260
+ # Person.create!({ title: "Mr" }, { title: "Mrs" })
261
+ #
262
+ # @param [ Hash, Array ] attributes The attributes to create with, or an
263
+ # Array of multiple attributes for multiple documents.
245
264
  # @param [ Hash ] options A mass-assignment protection options. Supports
246
265
  # :as and :without_protection
247
266
  #
248
- # @return [ Document ] The newly created document.
267
+ # @return [ Document, Array<Document> ] The newly created document(s).
268
+ #
269
+ # @since 1.0.0
249
270
  def create!(attributes = {}, options = {}, &block)
250
271
  _creating do
251
- doc = new(attributes, options, &block)
252
- fail_validate!(doc) unless doc.insert.errors.empty?
253
- fail_callback!(doc, :create!) if doc.new_record?
254
- doc
272
+ if attributes.is_a?(::Array)
273
+ attributes.map { |attrs| create!(attrs, options, &block) }
274
+ else
275
+ doc = new(attributes, options, &block)
276
+ fail_validate!(doc) unless doc.insert.errors.empty?
277
+ fail_callback!(doc, :create!) if doc.new_record?
278
+ doc
279
+ end
255
280
  end
256
281
  end
257
282
 
@@ -209,17 +209,22 @@ module Mongoid
209
209
 
210
210
  # Performs the atomic $unset on the supplied field.
211
211
  #
212
- # @example Remove the field.
213
- # person.unset(:age)
212
+ # @example
213
+ # Remove the field.
214
+ # person.unset(:age)
215
+ # Remove fields age and score.
216
+ # person.unset([:age, :score])
214
217
  #
215
- # @param [ Symbol ] field The field name.
218
+ # @param [ Symbol, Array<Object> ] fields The fields name.
216
219
  # @param [ Hash ] options The mongo persistence options.
217
220
  #
218
221
  # @return [ nil ] Always nil.
219
222
  #
220
223
  # @since 2.1.0
221
- def unset(field, options = {})
222
- Unset.new(self, field, 1, options).persist
224
+ def unset(*args)
225
+ fields = args.__find_args__
226
+ options = fields[-1].is_a?(Hash) ? fields.delete_at(-1) : {}
227
+ Unset.new(self, fields, true, options).persist
223
228
  end
224
229
  end
225
230
  end
@@ -5,8 +5,9 @@ module Mongoid
5
5
 
6
6
  # This is the included module for all atomic operation objects.
7
7
  module Operation
8
+ include Mongoid::Atomic::Positionable
8
9
 
9
- attr_accessor :document, :field, :value, :options
10
+ attr_accessor :document, :fields, :value, :options
10
11
 
11
12
  # Get the collection to be used for persistence.
12
13
  #
@@ -31,10 +32,18 @@ module Mongoid
31
32
  # @param [ Hash ] options The persistence options.
32
33
  #
33
34
  # @since 2.0.0
34
- def initialize(document, field, value, options = {})
35
- @document, @field, @value =
36
- document, document.database_field_name(field.to_s), value
35
+ def initialize(document, fields, value, options = {})
36
+ @document, @value = document, value
37
37
  @options = options
38
+
39
+ @fields = Array.wrap(fields).collect do |field|
40
+ document.database_field_name(field.to_s)
41
+ end
42
+
43
+ self.class.send(:define_method, :field) do
44
+ @fields.first
45
+ end if @fields.length == 1
46
+
38
47
  end
39
48
 
40
49
  # Get the atomic operation to perform.
@@ -48,7 +57,10 @@ module Mongoid
48
57
  #
49
58
  # @since 2.0.0
50
59
  def operation(modifier)
51
- { modifier => { path => cast_value } }
60
+ hash = Hash[fields.collect do |field|
61
+ [path(field), cast_value]
62
+ end]
63
+ { modifier => hash }
52
64
  end
53
65
 
54
66
  # Get the path to the field that is getting atomically updated.
@@ -59,7 +71,7 @@ module Mongoid
59
71
  # @return [ String, Symbol ] The path to the field.
60
72
  #
61
73
  # @since 2.1.0
62
- def path
74
+ def path(field = field)
63
75
  position = document.atomic_position
64
76
  position.blank? ? field : "#{position}.#{field}"
65
77
  end
@@ -108,9 +120,14 @@ module Mongoid
108
120
  #
109
121
  # @since 3.0.0
110
122
  def execute(name)
111
- if !document.new_record?
112
- collection.find(document.atomic_selector).update(operation(name))
113
- document.remove_change(field)
123
+ unless document.new_record?
124
+ selector = document.atomic_selector
125
+ collection.find(selector).update(positionally(selector, operation(name)))
126
+ if fields.length > 1
127
+ document.remove_change(fields)
128
+ else
129
+ document.remove_change(field)
130
+ end
114
131
  end
115
132
  end
116
133