mongoid 3.0.0.rc → 3.0.0

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