mongoid 3.0.0.rc → 3.0.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 (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
@@ -28,12 +28,10 @@ module Mongoid
28
28
  docs = args.flatten
29
29
  return concat(docs) if docs.size > 1
30
30
  if doc = docs.first
31
- if !target.include?(doc)
32
- append(doc)
33
- base.push(foreign_key, doc.id)
34
- if persistable? || _creating?
35
- doc.save
36
- end
31
+ append(doc)
32
+ base.add_to_set(foreign_key, doc.id)
33
+ if persistable? || _creating?
34
+ doc.save
37
35
  end
38
36
  end
39
37
  unsynced(base, foreign_key) and self
@@ -52,21 +50,24 @@ module Mongoid
52
50
  #
53
51
  # @since 2.4.0
54
52
  def concat(documents)
55
- ids, inserts = [], []
53
+ ids, docs, inserts = {}, [], []
56
54
  documents.each do |doc|
57
- next if doc.nil? || target.include?(doc)
55
+ next unless doc
58
56
  append(doc)
59
57
  if persistable? || _creating?
60
- ids.push(doc.id)
61
- save_or_delay(doc, inserts)
58
+ ids[doc.id] = true
59
+ save_or_delay(doc, docs, inserts)
62
60
  else
63
- base.send(foreign_key).push(doc.id) and unsynced(base, foreign_key)
61
+ existing = base.send(foreign_key)
62
+ unless existing.include?(doc.id)
63
+ existing.push(doc.id) and unsynced(base, foreign_key)
64
+ end
64
65
  end
65
66
  end
66
67
  if persistable? || _creating?
67
- base.push_all(foreign_key, ids)
68
+ base.push_all(foreign_key, ids.keys)
68
69
  end
69
- persist_delayed(inserts)
70
+ persist_delayed(docs, inserts)
70
71
  self
71
72
  end
72
73
 
@@ -284,23 +284,6 @@ module Mongoid
284
284
  SCOPE
285
285
  end
286
286
 
287
- # When inheriting, we want to copy the scopes from the parent class and
288
- # set the on the child to start, mimicking the behaviour of the old
289
- # class_inheritable_accessor that was deprecated in Rails edge.
290
- #
291
- # @api private
292
- #
293
- # @example Inherit from this class.
294
- # Person.inherited(Doctor)
295
- #
296
- # @param [ Class ] subclass The inheriting class.
297
- #
298
- # @since 2.0.0.rc.6
299
- def inherited(subclass)
300
- super
301
- subclass.scopes = scopes.dup
302
- end
303
-
304
287
  # Strip the default scope from the provided value, if it is a criteria.
305
288
  # This is used by named scopes - they should not have the default scoping
306
289
  # applied to them.
@@ -27,43 +27,112 @@ module Mongoid
27
27
  # @since 2.0.0.rc.6
28
28
  def serializable_hash(options = nil)
29
29
  options ||= {}
30
+ attrs = {}
30
31
 
31
- only = Array.wrap(options[:only]).map(&:to_s)
32
- except = Array.wrap(options[:except]).map(&:to_s)
33
-
34
- except |= ['_type'] unless Mongoid.include_type_for_serialization
35
-
36
- field_names = self.class.attribute_names
37
- attribute_names = (as_document.keys + field_names).sort
38
- if only.any?
39
- attribute_names &= only
40
- elsif except.any?
41
- attribute_names -= except
42
- end
32
+ names = field_names(options)
43
33
 
44
- method_names = Array.wrap(options[:methods]).map do |name|
45
- name.to_s if respond_to?(name)
46
- end.compact
34
+ _serializing do
35
+ method_names = Array.wrap(options[:methods]).map do |name|
36
+ name.to_s if respond_to?(name)
37
+ end.compact
47
38
 
48
- attrs = {}
49
- (attribute_names + method_names).each do |name|
50
- without_autobuild do
51
- if relations.has_key?(name)
52
- value = send(name)
53
- attrs[name] = value ? value.serializable_hash(options) : nil
54
- elsif attribute_names.include?(name) && !fields.has_key?(name)
55
- attrs[name] = read_attribute(name)
56
- else
57
- attrs[name] = send(name)
39
+ (names + method_names).each do |name|
40
+ without_autobuild do
41
+ serialize_attribute(attrs, name, names, options)
58
42
  end
59
43
  end
44
+ serialize_relations(attrs, options) if options[:include]
60
45
  end
61
- serialize_relations(attrs, options) if options[:include]
62
46
  attrs
63
47
  end
64
48
 
65
49
  private
66
50
 
51
+ # Enter the serialization block.
52
+ #
53
+ # @api private
54
+ #
55
+ # @example Begin serialization.
56
+ # document._serializing do
57
+ # end
58
+ #
59
+ # @return [ Object ] The result of the yield.
60
+ #
61
+ # @since 3.0.0
62
+ def _serializing
63
+ Threaded.begin("serialization")
64
+ yield
65
+ ensure
66
+ Threaded.exit("serialization")
67
+ end
68
+
69
+ # Are we in a serialization block? We use this to protect multiple
70
+ # unnecessary calls to #as_document.
71
+ #
72
+ # @api private
73
+ #
74
+ # @example Are we in serialization?
75
+ # document._serializing?
76
+ #
77
+ # @return [ true, false ] If we are serializing.
78
+ #
79
+ # @since 3.0.0
80
+ def _serializing?
81
+ Threaded.executing?("serialization")
82
+ end
83
+
84
+ # Get the names of all fields that will be serialized.
85
+ #
86
+ # @api private
87
+ #
88
+ # @example Get all the field names.
89
+ # document.send(:field_names)
90
+ #
91
+ # @return [ Array<String> ] The names of the fields.
92
+ #
93
+ # @since 3.0.0
94
+ def field_names(options)
95
+ names = (_serializing? ? attribute_names : as_document.keys + attribute_names).uniq.sort
96
+
97
+ only = Array.wrap(options[:only]).map(&:to_s)
98
+ except = Array.wrap(options[:except]).map(&:to_s)
99
+ except |= ['_type'] unless Mongoid.include_type_for_serialization
100
+
101
+ if !only.empty?
102
+ names &= only
103
+ elsif !except.empty?
104
+ names -= except
105
+ end
106
+ names
107
+ end
108
+
109
+ # Serialize a single attribute. Handles relations, fields, and dynamic
110
+ # attributes.
111
+ #
112
+ # @api private
113
+ #
114
+ # @example Serialize the attribute.
115
+ # document.serialize_attribute({}, "id" , [ "id" ])
116
+ #
117
+ # @param [ Hash ] attrs The attributes.
118
+ # @param [ String ] name The attribute name.
119
+ # @param [ Array<String> ] names The names of all attributes.
120
+ # @param [ Hash ] options The options.
121
+ #
122
+ # @return [ Object ] The attribute.
123
+ #
124
+ # @since 3.0.0
125
+ def serialize_attribute(attrs, name, names, options)
126
+ if relations.has_key?(name)
127
+ value = send(name)
128
+ attrs[name] = value ? value.serializable_hash(options) : nil
129
+ elsif names.include?(name) && !fields.has_key?(name)
130
+ attrs[name] = read_attribute(name)
131
+ else
132
+ attrs[name] = send(name)
133
+ end
134
+ end
135
+
67
136
  # For each of the provided include options, get the relation needed and
68
137
  # provide it in the hash.
69
138
  #
@@ -184,6 +184,20 @@ module Mongoid
184
184
  @database_name ||= __database_name__
185
185
  end
186
186
 
187
+ # Get the overridden database name. This either can be overridden by
188
+ # using +Model.with+ or by overriding at the global level via
189
+ # +Mongoid.override_database(:name)+.
190
+ #
191
+ # @example Get the overridden database name.
192
+ # Model.database_override
193
+ #
194
+ # @return [ String, Symbol ] The overridden database name.
195
+ #
196
+ # @since 3.0.0
197
+ def database_override
198
+ persistence_options.try { |opts| opts[:database] } || Threaded.database_override
199
+ end
200
+
187
201
  # Get the session for this model. This is determined in the following order:
188
202
  #
189
203
  # 1. Any custom configuration provided by the 'store_in' macro.
@@ -197,11 +211,7 @@ module Mongoid
197
211
  # @since 3.0.0
198
212
  def mongo_session
199
213
  session = __session__
200
- if persistence_options && name = persistence_options[:database]
201
- session.use(name)
202
- else
203
- session.use(database_name)
204
- end
214
+ session.use(database_override || database_name)
205
215
  session
206
216
  end
207
217
 
@@ -217,6 +227,20 @@ module Mongoid
217
227
  Threaded.persistence_options(self)
218
228
  end
219
229
 
230
+ # Get the overridden session name. This either can be overridden by
231
+ # using +Model.with+ or by overriding at the global level via
232
+ # +Mongoid.override_session(:name)+.
233
+ #
234
+ # @example Get the overridden session name.
235
+ # Model.session_override
236
+ #
237
+ # @return [ String, Symbol ] The overridden session name.
238
+ #
239
+ # @since 3.0.0
240
+ def session_override
241
+ persistence_options.try { |opts| opts[:session] } || Threaded.session_override
242
+ end
243
+
220
244
  # Give this model specific custom default storage options.
221
245
  #
222
246
  # @example Store this model by default in "artists"
@@ -346,7 +370,7 @@ module Mongoid
346
370
  #
347
371
  # @since 3.0.0
348
372
  def __session__
349
- if persistence_options && name = persistence_options[:session]
373
+ if name = session_override
350
374
  Sessions.with_name(name)
351
375
  elsif storage_options && name = storage_options[:session]
352
376
  Sessions.with_name(name)
@@ -58,7 +58,9 @@ module Mongoid
58
58
  #
59
59
  # @since 3.0.0
60
60
  def create_session(configuration)
61
+ raise Errors::NoSessionsConfig.new unless configuration
61
62
  config, options = parse(configuration)
63
+ configuration.merge!(config) if configuration.delete(:uri)
62
64
  session = Moped::Session.new(config[:hosts], options)
63
65
  session.use(config[:database])
64
66
  if authenticated?(config)
@@ -22,6 +22,32 @@ module Mongoid
22
22
  stack(name).push(true)
23
23
  end
24
24
 
25
+ # Get the global database override.
26
+ #
27
+ # @example Get the global database override.
28
+ # Threaded.database_override
29
+ #
30
+ # @return [ String, Symbol ] The override.
31
+ #
32
+ # @since 3.0.0
33
+ def database_override
34
+ Thread.current["[mongoid]:db-override"]
35
+ end
36
+
37
+ # Set the global database override.
38
+ #
39
+ # @example Set the global database override.
40
+ # Threaded.database_override = :testing
41
+ #
42
+ # @param [ String, Symbol ] The global override name.
43
+ #
44
+ # @return [ String, Symbol ] The override.
45
+ #
46
+ # @since 3.0.0
47
+ def database_override=(name)
48
+ Thread.current["[mongoid]:db-override"] = name
49
+ end
50
+
25
51
  # Get the database sessions from the current thread.
26
52
  #
27
53
  # @example Get the database sessions.
@@ -296,6 +322,32 @@ module Mongoid
296
322
  Thread.current["[mongoid]:selection"] = value
297
323
  end
298
324
 
325
+ # Get the global session override.
326
+ #
327
+ # @example Get the global session override.
328
+ # Threaded.session_override
329
+ #
330
+ # @return [ String, Symbol ] The override.
331
+ #
332
+ # @since 3.0.0
333
+ def session_override
334
+ Thread.current["[mongoid]:session-override"]
335
+ end
336
+
337
+ # Set the global session override.
338
+ #
339
+ # @example Set the global session override.
340
+ # Threaded.session_override = :testing
341
+ #
342
+ # @param [ String, Symbol ] The global override name.
343
+ #
344
+ # @return [ String, Symbol ] The override.
345
+ #
346
+ # @since 3.0.0
347
+ def session_override=(name)
348
+ Thread.current["[mongoid]:session-override"] = name
349
+ end
350
+
299
351
  # Get the mongoid scope stack for chained criteria.
300
352
  #
301
353
  # @example Get the scope stack.
@@ -19,7 +19,7 @@ module Mongoid
19
19
  def set_created_at
20
20
  if !created_at
21
21
  time = Time.now.utc
22
- self.updated_at = time if is_a?(Updated)
22
+ self.updated_at = time if is_a?(Updated) && !updated_at_changed?
23
23
  self.created_at = time
24
24
  end
25
25
  end
@@ -8,7 +8,8 @@ module Mongoid
8
8
 
9
9
  included do
10
10
  field :updated_at, type: Time
11
- set_callback :save, :before, :set_updated_at, if: :able_to_set_updated_at?
11
+ set_callback :create, :before, :set_updated_at, if: :able_to_set_updated_at?
12
+ set_callback :update, :before, :set_updated_at, if: :able_to_set_updated_at?
12
13
  end
13
14
 
14
15
  # Update the updated_at field on the Document to the current time.
@@ -145,7 +145,7 @@ module Mongoid
145
145
  #
146
146
  # @since 2.3.0
147
147
  def filter(value)
148
- !case_sensitive? && value ? /^#{Regexp.escape(value.to_s)}$/i : value
148
+ !case_sensitive? && value ? /\A#{Regexp.escape(value.to_s)}$/i : value
149
149
  end
150
150
 
151
151
  # Scope the criteria to the scope options provided.
@@ -165,6 +165,7 @@ module Mongoid
165
165
  Array.wrap(options[:scope]).each do |item|
166
166
  criteria = criteria.where(item => document.attributes[item.to_s])
167
167
  end
168
+ criteria = criteria.where(deleted_at: nil) if document.paranoid?
168
169
  criteria
169
170
  end
170
171
 
@@ -242,7 +243,7 @@ module Mongoid
242
243
  # @since 2.4.10
243
244
  def validate_embedded(document, attribute, value)
244
245
  return if skip_validation?(document)
245
- relation = document._parent.send(document.metadata.name)
246
+ relation = document._parent.send(document.metadata_name)
246
247
  criteria = create_criteria(relation, document, attribute, value)
247
248
  add_error(document, attribute, value) if criteria.count > 1
248
249
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "3.0.0.rc"
3
+ VERSION = "3.0.0"
4
4
  end
@@ -58,3 +58,11 @@ development:
58
58
 
59
59
  # Ensure all times are UTC in the app side. (default: false)
60
60
  # use_utc: false
61
+ test:
62
+ sessions:
63
+ default:
64
+ database: <%= database_name || app_name %>_test
65
+ hosts:
66
+ - localhost:27017
67
+ options:
68
+ consistency: :strong
@@ -20,9 +20,12 @@ module Rails
20
20
  next if model.index_options.empty?
21
21
  unless model.embedded?
22
22
  model.create_indexes
23
- logger.info("Creating indexes on: #{model} for: #{model.index_options.keys.join(", ")}.")
23
+ logger.info("MONGOID: Created indexes on #{model}:")
24
+ model.index_options.each_pair do |index, options|
25
+ logger.info("MONGOID: Index: #{index}, Options: #{options}")
26
+ end
24
27
  else
25
- logger.info("Index ignored on: #{model}, please define in the root model.")
28
+ logger.info("MONGOID: Index ignored on: #{model}, please define in the root model.")
26
29
  end
27
30
  end
28
31
  end
@@ -44,7 +47,7 @@ module Rails
44
47
  indexes = model.collection.indexes.map{ |doc| doc["name"] }
45
48
  indexes.delete_one("_id_")
46
49
  model.remove_indexes
47
- logger.info("Removing indexes on: #{model} for: #{indexes.join(', ')}.")
50
+ logger.info("MONGOID: Removing indexes on: #{model} for: #{indexes.join(', ')}.")
48
51
  end
49
52
  end
50
53
 
@@ -63,7 +66,7 @@ module Rails
63
66
  begin
64
67
  determine_model(file, logger)
65
68
  rescue => e
66
- logger.error(%Q{Failed to determine model from #{file}:
69
+ logger.error(%Q{MONGOID: Failed to determine model from #{file}:
67
70
  #{e.class}:#{e.message}
68
71
  #{e.backtrace.join("\n")}
69
72
  })
@@ -138,7 +141,7 @@ module Rails
138
141
  name = parts.join("::")
139
142
  klass = name.constantize
140
143
  rescue NameError, LoadError
141
- logger.info("Attempted to constantize #{name}, trying without namespacing.")
144
+ logger.info("MONGOID: Attempted to constantize #{name}, trying without namespacing.")
142
145
  klass = parts.last.constantize
143
146
  end
144
147
  klass if klass.ancestors.include?(::Mongoid::Document)