mongoid 3.0.23 → 3.1.0

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