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
@@ -20,7 +20,7 @@ module Mongoid
20
20
  #
21
21
  # @since 2.1.0
22
22
  def path
23
- position.sub(/\.\d+$/, "")
23
+ @path ||= position.sub(/\.\d+$/, "")
24
24
  end
25
25
 
26
26
  # Get the selector to use for the root document when performing atomic
@@ -33,10 +33,24 @@ module Mongoid
33
33
  #
34
34
  # @since 2.1.0
35
35
  def selector
36
- parent.atomic_selector
37
- # @todo: Durran: Bring this back once MongoDB, if ever, goes to fix
38
- # this issue: https://jira.mongodb.org/browse/SERVER-831
39
- # merge!({ "#{path}._id" => document._id }).merge!(document.shard_key_selector)
36
+ @selector ||= generate_selector
37
+ end
38
+
39
+ private
40
+
41
+ def generate_selector
42
+ if only_root_selector?
43
+ parent.atomic_selector
44
+ else
45
+ parent.
46
+ atomic_selector.
47
+ merge("#{path}._id" => document._id).
48
+ merge(document.shard_key_selector)
49
+ end
50
+ end
51
+
52
+ def only_root_selector?
53
+ document.persisted? && document._id_changed?
40
54
  end
41
55
  end
42
56
  end
@@ -44,7 +44,7 @@ module Mongoid
44
44
  #
45
45
  # @since 2.1.0
46
46
  def selector
47
- { "_id" => document._id }.merge!(document.shard_key_selector)
47
+ @selector ||= { "_id" => document._id }.merge!(document.shard_key_selector)
48
48
  end
49
49
  end
50
50
  end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Atomic
4
+
5
+ # This module is responsible for taking update selectors and switching out
6
+ # the indexes for the $ positional operator where appropriate.
7
+ #
8
+ # @since 3.1.0
9
+ module Positionable
10
+
11
+ # Takes the provided selector and atomic operations and replaces the
12
+ # indexes of the embedded documents with the positional operator when
13
+ # needed.
14
+ #
15
+ # @note The only time we can accurately know when to use the positional
16
+ # operator is at the exact time we are going to persist something. So
17
+ # we can tell by the selector that we are sending if it is actually
18
+ # possible to use the positional operator at all. For example, if the
19
+ # selector is: { "_id" => 1 }, then we could not use the positional
20
+ # operator for updating embedded documents since there would never be a
21
+ # match - we base whether we can based on the number of levels deep the
22
+ # selector goes, and if the id values are not nil.
23
+ #
24
+ # @example Process the operations.
25
+ # positionally(
26
+ # { "_id" => 1, "addresses._id" => 2 },
27
+ # { "$set" => { "addresses.0.street" => "hobrecht" }}
28
+ # )
29
+ #
30
+ # @param [ Hash ] selector The selector.
31
+ # @param [ Hash ] operations The update operations.
32
+ # @param [ Hash ] processed The processed update operations.
33
+ #
34
+ # @return [ Hash ] The new operations.
35
+ #
36
+ # @since 3.1.0
37
+ def positionally(selector, operations, processed = {})
38
+ if selector.size == 1 || selector.values.any? { |val| val.nil? }
39
+ return operations
40
+ end
41
+ keys = selector.keys.map{ |m| m.sub('._id','') } - ['_id']
42
+ keys = keys.sort_by { |s| s.length*-1 }
43
+ process_operations(keys, operations, processed)
44
+ end
45
+
46
+ private
47
+
48
+ def process_operations(keys, operations, processed)
49
+ operations.each_pair do |operation, update|
50
+ processed[operation] = process_updates(keys, update)
51
+ end
52
+ processed
53
+ end
54
+
55
+ def process_updates(keys, update, updates = {})
56
+ update.each_pair do |position, value|
57
+ updates[replace_index(keys, position)] = value
58
+ end
59
+ updates
60
+ end
61
+
62
+ def replace_index(keys, position)
63
+ # replace to $ only if that key is on the selector
64
+ keys.each do |kk|
65
+ if position =~ /^#{kk}\.\d+\.(.*)/
66
+ return "#{kk}.$.#{$1}"
67
+ end
68
+ end
69
+ position
70
+ end
71
+ end
72
+ end
73
+ end
@@ -12,6 +12,7 @@ module Mongoid
12
12
  include Readonly
13
13
 
14
14
  attr_reader :attributes
15
+ attr_reader :attributes_before_type_cast
15
16
  alias :raw_attributes :attributes
16
17
 
17
18
  # Determine if an attribute is present.
@@ -43,6 +44,22 @@ module Mongoid
43
44
  attributes.has_key?(name.to_s)
44
45
  end
45
46
 
47
+ # Does the document have the provided attribute before it was assigned
48
+ # and type cast?
49
+ #
50
+ # @example Does the document have the attribute before it was assigned?
51
+ # model.has_attribute_before_type_cast?(:name)
52
+ #
53
+ # @param [ String, Symbol ] name The name of the attribute.
54
+ #
55
+ # @return [ true, false ] If the key is present in the
56
+ # attributes_before_type_cast.
57
+ #
58
+ # @since 3.1.0
59
+ def has_attribute_before_type_cast?(name)
60
+ attributes_before_type_cast.has_key?(name.to_s)
61
+ end
62
+
46
63
  # Read a value from the document attributes. If the value does not exist
47
64
  # it will return nil.
48
65
  #
@@ -67,6 +84,28 @@ module Mongoid
67
84
  end
68
85
  alias :[] :read_attribute
69
86
 
87
+ # Read a value from the attributes before type cast. If the value has not
88
+ # yet been assigned then this will return the attribute's existing value
89
+ # using read_attribute.
90
+ #
91
+ # @example Read an attribute before type cast.
92
+ # person.read_attribute_before_type_cast(:price)
93
+ #
94
+ # @param [ String, Symbol ] name The name of the attribute to get.
95
+ #
96
+ # @return [ Object ] The value of the attribute before type cast, if
97
+ # available. Otherwise, the value of the attribute.
98
+ #
99
+ # @since 3.1.0
100
+ def read_attribute_before_type_cast(name)
101
+ attr = name.to_s
102
+ if attributes_before_type_cast.has_key?(attr)
103
+ attributes_before_type_cast[attr]
104
+ else
105
+ read_attribute(attr)
106
+ end
107
+ end
108
+
70
109
  # Remove a value from the +Document+ attributes. If the value does not exist
71
110
  # it will fail gracefully.
72
111
  #
@@ -129,6 +168,7 @@ module Mongoid
129
168
  if attribute_writable?(access)
130
169
  _assigning do
131
170
  localized = fields[access].try(:localized?)
171
+ attributes_before_type_cast[name.to_s] = value
132
172
  typed_value = typed_value_for(access, value)
133
173
  unless attributes[access] == typed_value || attribute_changed?(access)
134
174
  attribute_will_change!(access)
@@ -206,6 +246,24 @@ module Mongoid
206
246
  READER
207
247
  end
208
248
 
249
+ # Define a reader method for a dynamic attribute before type cast.
250
+ #
251
+ # @api private
252
+ #
253
+ # @example Define a reader method for an attribute.
254
+ # model.define_dynamic_before_type_cast_reader(:field)
255
+ #
256
+ # @param [ String ] name The name of the field.
257
+ #
258
+ # @since 3.1.0
259
+ def define_dynamic_before_type_cast_reader(name)
260
+ class_eval <<-READER
261
+ def #{name}_before_type_cast
262
+ read_attribute_before_type_cast(#{name.inspect})
263
+ end
264
+ READER
265
+ end
266
+
209
267
  # Define a writer method for a dynamic attribute.
210
268
  #
211
269
  # @api private
@@ -251,6 +309,9 @@ module Mongoid
251
309
  getter = attr.reader
252
310
  define_dynamic_writer(getter)
253
311
  write_attribute(getter, args.first)
312
+ elsif attr.before_type_cast?
313
+ define_dynamic_before_type_cast_reader(attr.reader)
314
+ read_attribute_before_type_cast(attr.reader)
254
315
  else
255
316
  getter = attr.reader
256
317
  define_dynamic_reader(getter)
@@ -270,7 +331,7 @@ module Mongoid
270
331
  #
271
332
  # @since 1.0.0
272
333
  def typed_value_for(key, value)
273
- fields.has_key?(key) ? fields[key].mongoize(value) : value.mongoize
334
+ fields.has_key?(key) ? fields[key].mongoize(value) : value
274
335
  end
275
336
 
276
337
  module ClassMethods
@@ -300,6 +361,7 @@ module Mongoid
300
361
  alias reset_#{name}! reset_#{original}!
301
362
  alias #{name}_was #{original}_was
302
363
  alias #{name}_will_change! #{original}_will_change!
364
+ alias #{name}_before_type_cast #{original}_before_type_cast
303
365
  RUBY
304
366
  end
305
367
  end
@@ -9,8 +9,10 @@ module Mongoid
9
9
  :after_build,
10
10
  :after_create,
11
11
  :after_destroy,
12
+ :after_find,
12
13
  :after_initialize,
13
14
  :after_save,
15
+ :after_touch,
14
16
  :after_update,
15
17
  :after_upsert,
16
18
  :after_validation,
@@ -25,14 +27,13 @@ module Mongoid
25
27
  :before_update,
26
28
  :before_upsert,
27
29
  :before_validation
28
- ]
30
+ ].freeze
29
31
 
30
32
  included do
31
33
  extend ActiveModel::Callbacks
32
34
  include ActiveModel::Validations::Callbacks
33
35
 
34
- define_model_callbacks :initialize, only: :after
35
- define_model_callbacks :build, only: :after
36
+ define_model_callbacks :build, :find, :initialize, :touch, only: :after
36
37
  define_model_callbacks :create, :destroy, :save, :update, :upsert
37
38
 
38
39
  attr_accessor :before_callback_halted
@@ -187,7 +188,9 @@ module Mongoid
187
188
  #
188
189
  # @since 2.3.0
189
190
  def cascadable_child?(kind, child)
190
- return false if kind == :initialize
191
+ if kind == :initialize || kind == :find
192
+ return false
193
+ end
191
194
  child.callback_executable?(kind) ? child.in_callback_state?(kind) : false
192
195
  end
193
196
 
@@ -256,5 +259,56 @@ module Mongoid
256
259
  end
257
260
  send(name)
258
261
  end
262
+
263
+ class << self
264
+
265
+ # Get all callbacks that can be observed.
266
+ #
267
+ # @example Get the observables.
268
+ # Callbacks.observables
269
+ #
270
+ # @return [ Array<Symbol> ] The names of the observables.
271
+ #
272
+ # @since 3.1.0
273
+ def observables
274
+ CALLBACKS + registered_observables
275
+ end
276
+
277
+ # Get all registered callbacks that can be observed, not included in
278
+ # Mongoid's defaults.
279
+ #
280
+ # @example Get the observables.
281
+ # Callbacks.registered_observables
282
+ #
283
+ # @return [ Array<Symbol> ] The names of the registered observables.
284
+ #
285
+ # @since 3.1.0
286
+ def registered_observables
287
+ @registered_observables ||= []
288
+ end
289
+ end
290
+
291
+ module ClassMethods
292
+
293
+ # Set a custom callback as able to be observed.
294
+ #
295
+ # @example Set a custom callback as observable.
296
+ # class Band
297
+ # include Mongoid::Document
298
+ #
299
+ # define_model_callbacks :notification
300
+ # observable :notification
301
+ # end
302
+ #
303
+ # @param [ Array<Symbol> ] args The names of the observable callbacks.
304
+ #
305
+ # @since 3.0.1
306
+ def observable(*args)
307
+ observables = args.flat_map do |name|
308
+ [ :"before_#{name}", :"after_#{name}", :"around_#{name}" ]
309
+ end
310
+ Callbacks.registered_observables.concat(observables).uniq
311
+ end
312
+ end
259
313
  end
260
314
  end
@@ -42,29 +42,34 @@ module Mongoid
42
42
  include Mongoid::Validations
43
43
  include Mongoid::Callbacks
44
44
  include Mongoid::Copyable
45
+ include Mongoid::Equality
45
46
 
46
47
  MODULES = [
47
48
  Mongoid::Atomic,
48
49
  Mongoid::Attributes,
50
+ Mongoid::Callbacks,
49
51
  Mongoid::Copyable,
50
52
  Mongoid::Dirty,
53
+ Mongoid::Evolvable,
51
54
  Mongoid::Fields,
52
55
  Mongoid::Hierarchy,
53
56
  Mongoid::Indexes,
54
57
  Mongoid::Inspection,
55
58
  Mongoid::JSON,
56
- Mongoid::Loggable,
57
59
  Mongoid::Matchers,
58
60
  Mongoid::NestedAttributes,
59
61
  Mongoid::Persistence,
60
62
  Mongoid::Relations,
61
- Mongoid::Relations::Proxy,
63
+ Mongoid::Reloading,
62
64
  Mongoid::Scoping,
63
65
  Mongoid::Serialization,
66
+ Mongoid::Sessions,
64
67
  Mongoid::Sharding,
65
68
  Mongoid::State,
69
+ Mongoid::Threaded::Lifecycle,
70
+ Mongoid::Timestamps::Timeless,
66
71
  Mongoid::Validations,
67
- Mongoid::Callbacks
72
+ Mongoid::Equality
68
73
  ]
69
74
 
70
75
  class << self
@@ -13,6 +13,11 @@ module Mongoid
13
13
  extend Options
14
14
  include ActiveModel::Observing
15
15
 
16
+ delegate :logger=, to: ::Mongoid
17
+ delegate :logger, to: ::Mongoid
18
+
19
+ LOCK = Mutex.new
20
+
16
21
  option :allow_dynamic_fields, default: true
17
22
  option :identity_map_enabled, default: false
18
23
  option :include_root_in_json, default: false
@@ -86,6 +91,47 @@ module Mongoid
86
91
  settings
87
92
  end
88
93
 
94
+ # Get all the models in the application - this is everything that includes
95
+ # Mongoid::Document.
96
+ #
97
+ # @example Get all the models.
98
+ # config.models
99
+ #
100
+ # @return [ Array<Class> ] All the models in the application.
101
+ #
102
+ # @since 3.1.0
103
+ def models
104
+ @models ||= []
105
+ end
106
+
107
+ # Register a model in the application with Mongoid.
108
+ #
109
+ # @example Register a model.
110
+ # config.register_model(Band)
111
+ #
112
+ # @param [ Class ] klass The model to register.
113
+ #
114
+ # @since 3.1.0
115
+ def register_model(klass)
116
+ LOCK.synchronize do
117
+ models.push(klass) unless models.include?(klass)
118
+ end
119
+ end
120
+
121
+ # From a hash of settings, load all the configuration.
122
+ #
123
+ # @example Load the configuration.
124
+ # config.load_configuration(settings)
125
+ #
126
+ # @param [ Hash ] settings The configuration settings.
127
+ #
128
+ # @since 3.1.0
129
+ def load_configuration(settings)
130
+ configuration = settings.with_indifferent_access
131
+ self.options = configuration[:options]
132
+ self.sessions = configuration[:sessions]
133
+ end
134
+
89
135
  # Override the database to use globally.
90
136
  #
91
137
  # @example Override the database globally.
@@ -119,6 +165,8 @@ module Mongoid
119
165
  # @example Purge all data.
120
166
  # Mongoid::Config.purge!
121
167
  #
168
+ # @note This is the fastest way to drop all data.
169
+ #
122
170
  # @return [ true ] true.
123
171
  #
124
172
  # @since 2.0.2
@@ -128,6 +176,22 @@ module Mongoid
128
176
  end and true
129
177
  end
130
178
 
179
+ # Truncate all data in all collections, but not the indexes.
180
+ #
181
+ # @example Truncate all collection data.
182
+ # Mongoid::Config.truncate!
183
+ #
184
+ # @note This will be slower than purge!
185
+ #
186
+ # @return [ true ] true.
187
+ #
188
+ # @since 2.0.2
189
+ def truncate!
190
+ Sessions.default.collections.each do |collection|
191
+ collection.find.remove_all
192
+ end and true
193
+ end
194
+
131
195
  # Set the configuration options. Will validate each one individually.
132
196
  #
133
197
  # @example Set the options.
@@ -145,18 +209,6 @@ module Mongoid
145
209
  end
146
210
  end
147
211
 
148
- # Is the application running under passenger?
149
- #
150
- # @example Is the application using passenger?
151
- # config.running_with_passenger?
152
- #
153
- # @return [ true, false ] If the app is deployed on Passenger.
154
- #
155
- # @since 3.0.11
156
- def running_with_passenger?
157
- @running_with_passenger ||= defined?(PhusionPassenger)
158
- end
159
-
160
212
  # Get the session configuration or an empty hash.
161
213
  #
162
214
  # @example Get the sessions configuration.
@@ -197,20 +249,16 @@ module Mongoid
197
249
  use_utc? ? "UTC" : ::Time.zone
198
250
  end
199
251
 
200
- private
201
-
202
- # From a hash of settings, load all the configuration.
252
+ # Is the application running under passenger?
203
253
  #
204
- # @example Load the configuration.
205
- # config.load_configuration(settings)
254
+ # @example Is the application using passenger?
255
+ # config.running_with_passenger?
206
256
  #
207
- # @param [ Hash ] settings The configuration settings.
257
+ # @return [ true, false ] If the app is deployed on Passenger.
208
258
  #
209
- # @since 3.0.0
210
- def load_configuration(settings)
211
- configuration = settings.with_indifferent_access
212
- self.options = configuration[:options]
213
- self.sessions = configuration[:sessions]
259
+ # @since 3.0.11
260
+ def running_with_passenger?
261
+ @running_with_passenger ||= defined?(PhusionPassenger)
214
262
  end
215
263
  end
216
264
  end