mongoid 2.2.6 → 2.3.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 (80) hide show
  1. data/CHANGELOG.md +2 -858
  2. data/Rakefile +2 -5
  3. data/lib/mongoid.rb +1 -1
  4. data/lib/mongoid/attributes.rb +68 -18
  5. data/lib/mongoid/attributes/processing.rb +4 -3
  6. data/lib/mongoid/callbacks.rb +102 -0
  7. data/lib/mongoid/collection.rb +1 -1
  8. data/lib/mongoid/components.rb +2 -1
  9. data/lib/mongoid/contexts/enumerable.rb +0 -24
  10. data/lib/mongoid/contexts/mongo.rb +2 -2
  11. data/lib/mongoid/copyable.rb +3 -1
  12. data/lib/mongoid/criteria.rb +18 -10
  13. data/lib/mongoid/criterion/complex.rb +11 -0
  14. data/lib/mongoid/criterion/inclusion.rb +38 -7
  15. data/lib/mongoid/criterion/optional.rb +2 -7
  16. data/lib/mongoid/criterion/selector.rb +1 -0
  17. data/lib/mongoid/dirty.rb +19 -0
  18. data/lib/mongoid/document.rb +16 -12
  19. data/lib/mongoid/extensions/hash/criteria_helpers.rb +1 -1
  20. data/lib/mongoid/extensions/object/checks.rb +4 -1
  21. data/lib/mongoid/extensions/object_id/conversions.rb +4 -2
  22. data/lib/mongoid/extensions/string/inflections.rb +2 -2
  23. data/lib/mongoid/factory.rb +7 -2
  24. data/lib/mongoid/fields.rb +4 -10
  25. data/lib/mongoid/fields/serializable.rb +18 -2
  26. data/lib/mongoid/fields/serializable/integer.rb +17 -5
  27. data/lib/mongoid/fields/serializable/localized.rb +41 -0
  28. data/lib/mongoid/finders.rb +5 -4
  29. data/lib/mongoid/hierarchy.rb +87 -84
  30. data/lib/mongoid/identity.rb +4 -2
  31. data/lib/mongoid/keys.rb +2 -1
  32. data/lib/mongoid/logger.rb +1 -7
  33. data/lib/mongoid/matchers/and.rb +30 -0
  34. data/lib/mongoid/matchers/in.rb +1 -1
  35. data/lib/mongoid/matchers/nin.rb +1 -1
  36. data/lib/mongoid/matchers/strategies.rb +6 -4
  37. data/lib/mongoid/multi_parameter_attributes.rb +3 -2
  38. data/lib/mongoid/named_scope.rb +3 -13
  39. data/lib/mongoid/nested_attributes.rb +1 -1
  40. data/lib/mongoid/paranoia.rb +2 -3
  41. data/lib/mongoid/persistence.rb +9 -5
  42. data/lib/mongoid/persistence/atomic/operation.rb +1 -1
  43. data/lib/mongoid/persistence/deletion.rb +1 -1
  44. data/lib/mongoid/persistence/insertion.rb +1 -1
  45. data/lib/mongoid/persistence/modification.rb +1 -1
  46. data/lib/mongoid/railtie.rb +1 -1
  47. data/lib/mongoid/railties/database.rake +9 -1
  48. data/lib/mongoid/relations.rb +1 -0
  49. data/lib/mongoid/relations/accessors.rb +1 -1
  50. data/lib/mongoid/relations/builders.rb +6 -4
  51. data/lib/mongoid/relations/builders/referenced/many.rb +1 -23
  52. data/lib/mongoid/relations/builders/referenced/one.rb +1 -1
  53. data/lib/mongoid/relations/cascading.rb +5 -3
  54. data/lib/mongoid/relations/conversions.rb +35 -0
  55. data/lib/mongoid/relations/embedded/atomic.rb +3 -3
  56. data/lib/mongoid/relations/embedded/in.rb +1 -1
  57. data/lib/mongoid/relations/embedded/many.rb +16 -13
  58. data/lib/mongoid/relations/embedded/one.rb +3 -3
  59. data/lib/mongoid/relations/metadata.rb +19 -15
  60. data/lib/mongoid/relations/proxy.rb +4 -5
  61. data/lib/mongoid/relations/referenced/in.rb +1 -1
  62. data/lib/mongoid/relations/referenced/many.rb +12 -31
  63. data/lib/mongoid/relations/referenced/many_to_many.rb +4 -5
  64. data/lib/mongoid/relations/referenced/one.rb +6 -8
  65. data/lib/mongoid/relations/synchronization.rb +3 -5
  66. data/lib/mongoid/safety.rb +34 -4
  67. data/lib/mongoid/serialization.rb +20 -6
  68. data/lib/mongoid/threaded.rb +47 -0
  69. data/lib/mongoid/timestamps.rb +1 -0
  70. data/lib/mongoid/timestamps/created.rb +1 -8
  71. data/lib/mongoid/timestamps/timeless.rb +50 -0
  72. data/lib/mongoid/timestamps/updated.rb +2 -9
  73. data/lib/mongoid/validations.rb +0 -2
  74. data/lib/mongoid/validations/associated.rb +1 -2
  75. data/lib/mongoid/validations/uniqueness.rb +89 -36
  76. data/lib/mongoid/version.rb +1 -1
  77. data/lib/mongoid/versioning.rb +5 -6
  78. data/lib/rails/generators/mongoid_generator.rb +1 -1
  79. data/lib/rails/mongoid.rb +14 -5
  80. metadata +27 -23
@@ -71,7 +71,7 @@ module Mongoid # :nodoc:
71
71
  #
72
72
  # @since 2.1.0
73
73
  def persistable?
74
- target.persisted? && !binding? && !building?
74
+ target.persisted? && !binding? && !_building?
75
75
  end
76
76
 
77
77
  class << self
@@ -33,7 +33,7 @@ module Mongoid #:nodoc:
33
33
  args.flatten.each do |doc|
34
34
  next unless doc
35
35
  append(doc)
36
- doc.save if persistable? && !doc.validated?
36
+ doc.save if persistable?
37
37
  end
38
38
  end
39
39
  end
@@ -47,13 +47,14 @@ module Mongoid #:nodoc:
47
47
  # person.posts.build(:title => "A new post")
48
48
  #
49
49
  # @param [ Hash ] attributes The attributes of the new document.
50
+ # @param [ Hash ] options The scoped assignment options.
50
51
  # @param [ Class ] type The optional subclass to build.
51
52
  #
52
53
  # @return [ Document ] The new document.
53
54
  #
54
55
  # @since 2.0.0.beta.1
55
- def build(attributes = {}, type = nil)
56
- Factory.build(type || klass, attributes).tap do |doc|
56
+ def build(attributes = {}, options = {}, type = nil)
57
+ Factory.build(type || klass, attributes, options).tap do |doc|
57
58
  append(doc)
58
59
  yield(doc) if block_given?
59
60
  end
@@ -67,13 +68,14 @@ module Mongoid #:nodoc:
67
68
  # person.posts.create(:text => "Testing")
68
69
  #
69
70
  # @param [ Hash ] attributes The attributes to create with.
71
+ # @param [ Hash ] options The scoped assignment options.
70
72
  # @param [ Class ] type The optional type of document to create.
71
73
  #
72
74
  # @return [ Document ] The newly created document.
73
75
  #
74
76
  # @since 2.0.0.beta.1
75
- def create(attributes = nil, type = nil, &block)
76
- build(attributes, type, &block).tap do |doc|
77
+ def create(attributes = nil, options = {}, type = nil, &block)
78
+ build(attributes, options, type, &block).tap do |doc|
77
79
  base.persisted? ? doc.save : raise_unsaved(doc)
78
80
  end
79
81
  end
@@ -86,6 +88,7 @@ module Mongoid #:nodoc:
86
88
  # person.posts.create!(:text => "Testing")
87
89
  #
88
90
  # @param [ Hash ] attributes The attributes to create with.
91
+ # @param [ Hash ] options The scoped assignment options.
89
92
  # @param [ Class ] type The optional type of document to create.
90
93
  #
91
94
  # @raise [ Errors::Validations ] If validation failed.
@@ -93,8 +96,8 @@ module Mongoid #:nodoc:
93
96
  # @return [ Document ] The newly created document.
94
97
  #
95
98
  # @since 2.0.0.beta.1
96
- def create!(attributes = nil, type = nil, &block)
97
- build(attributes, type, &block).tap do |doc|
99
+ def create!(attributes = nil, options = {}, type = nil, &block)
100
+ build(attributes, options, type, &block).tap do |doc|
98
101
  base.persisted? ? doc.save! : raise_unsaved(doc)
99
102
  end
100
103
  end
@@ -321,28 +324,6 @@ module Mongoid #:nodoc:
321
324
  klass.collection
322
325
  end
323
326
 
324
- # Get the value for the foreign key in convertable or unconvertable
325
- # form.
326
- #
327
- # @todo Durran: Find a common place for this.
328
- #
329
- # @example Get the value.
330
- # relation.convertable
331
- #
332
- # @return [ String, BSON::ObjectId ] The string or object id.
333
- #
334
- # @since 2.0.2
335
- def convertable
336
- inverse = metadata.inverse_klass
337
- if inverse.using_object_ids? || base.id.is_a?(BSON::ObjectId)
338
- base.id
339
- else
340
- base.id.tap do |id|
341
- id.unconvertable_to_bson = true if id.is_a?(String)
342
- end
343
- end
344
- end
345
-
346
327
  # Returns the criteria object for the target class with its documents set
347
328
  # to target.
348
329
  #
@@ -353,7 +334,7 @@ module Mongoid #:nodoc:
353
334
  #
354
335
  # @since 2.0.0.beta.1
355
336
  def criteria
356
- Many.criteria(metadata, convertable)
337
+ Many.criteria(metadata, Conversions.flag(base.id, metadata))
357
338
  end
358
339
 
359
340
  # Perform the necessary cascade operations for documents that just got
@@ -408,7 +389,7 @@ module Mongoid #:nodoc:
408
389
  #
409
390
  # @since 2.1.0
410
391
  def persistable?
411
- base.persisted? && !binding? && !building?
392
+ base.persisted? && !binding? && !_building?
412
393
  end
413
394
 
414
395
  # Deletes all related documents from the database given the supplied
@@ -55,13 +55,14 @@ module Mongoid # :nodoc:
55
55
  # person.posts.build(:title => "A new post")
56
56
  #
57
57
  # @param [ Hash ] attributes The attributes of the new document.
58
+ # @param [ Hash ] options The scoped assignment options.
58
59
  # @param [ Class ] type The optional subclass to build.
59
60
  #
60
61
  # @return [ Document ] The new document.
61
62
  #
62
63
  # @since 2.0.0.beta.1
63
- def build(attributes = {}, type = nil)
64
- Factory.build(type || klass, attributes).tap do |doc|
64
+ def build(attributes = {}, options = {}, type = nil)
65
+ Factory.build(type || klass, attributes, options).tap do |doc|
65
66
  base.send(metadata.foreign_key).push(doc.id)
66
67
  append(doc)
67
68
  yield(doc) if block_given?
@@ -142,9 +143,7 @@ module Mongoid # :nodoc:
142
143
  #
143
144
  # @since 2.0.0.rc.1
144
145
  def nullify
145
- unless metadata.forced_nil_inverse?
146
- criteria.pull(metadata.inverse_foreign_key, base.id)
147
- end
146
+ criteria.pull(metadata.inverse_foreign_key, base.id)
148
147
  if persistable?
149
148
  base.set(
150
149
  metadata.foreign_key,
@@ -51,14 +51,12 @@ module Mongoid # :nodoc:
51
51
  #
52
52
  # @since 2.0.0.rc.1
53
53
  def substitute(replacement)
54
- tap do |proxy|
55
- proxy.unbind_one
56
- proxy.target.delete if persistable?
57
- return nil unless replacement
58
- proxy.target = replacement
59
- proxy.bind_one
60
- replacement.save if persistable?
54
+ unbind_one
55
+ if persistable?
56
+ metadata.destructive? ? send(metadata.dependent) : save
61
57
  end
58
+ return nil unless replacement
59
+ One.new(base, replacement, metadata)
62
60
  end
63
61
 
64
62
  private
@@ -84,7 +82,7 @@ module Mongoid # :nodoc:
84
82
  #
85
83
  # @since 2.1.0
86
84
  def persistable?
87
- base.persisted? && !binding? && !building?
85
+ base.persisted? && !binding? && !_building?
88
86
  end
89
87
 
90
88
  class << self
@@ -77,7 +77,7 @@ module Mongoid # :nodoc:
77
77
  def update_inverse_keys(meta)
78
78
  return unless changes.has_key?(meta.foreign_key)
79
79
  old, new = changes[meta.foreign_key]
80
- adds, subs = new - old, old - new
80
+ adds, subs = new - (old || []), (old || []) - new
81
81
  meta.criteria(adds).add_to_set(meta.inverse_foreign_key, id) unless adds.empty?
82
82
  meta.criteria(subs).pull(meta.inverse_foreign_key, id) unless subs.empty?
83
83
  end
@@ -93,10 +93,8 @@ module Mongoid # :nodoc:
93
93
  #
94
94
  # @since 2.1.0
95
95
  def synced(metadata)
96
- unless metadata.forced_nil_inverse?
97
- synced_save(metadata)
98
- synced_destroy(metadata)
99
- end
96
+ synced_save(metadata)
97
+ synced_destroy(metadata)
100
98
  end
101
99
 
102
100
  private
@@ -7,7 +7,7 @@ module Mongoid #:nodoc:
7
7
  module Safety
8
8
  extend ActiveSupport::Concern
9
9
 
10
- # Execute the following class-level persistence operation in safe mode.
10
+ # Execute the following instance-level persistence operation in safe mode.
11
11
  #
12
12
  # @example Upsert in safe mode.
13
13
  # person.safely.upsert
@@ -27,6 +27,18 @@ module Mongoid #:nodoc:
27
27
  tap { Threaded.safety_options = safety }
28
28
  end
29
29
 
30
+ # Execute the following instance-level persistence operation without safe mode.
31
+ # Allows per-request overriding of safe mode when the persist_in_safe_mode
32
+ # config option is turned on.
33
+ #
34
+ # @example Upsert in safe mode.
35
+ # person.unsafely.upsert
36
+ #
37
+ # @return [ Proxy ] The safety proxy.
38
+ def unsafely
39
+ tap { Threaded.safety_options = false }
40
+ end
41
+
30
42
  class << self
31
43
 
32
44
  # Static class method of easily getting the desired safe mode options
@@ -43,9 +55,13 @@ module Mongoid #:nodoc:
43
55
  def merge_safety_options(options = {})
44
56
  options ||= {}
45
57
  return options if options[:safe]
46
- options.merge!(
47
- { :safe => Threaded.safety_options || Mongoid.persist_in_safe_mode }
48
- )
58
+
59
+ unless Threaded.safety_options.nil?
60
+ safety = Threaded.safety_options
61
+ else
62
+ safety = Mongoid.persist_in_safe_mode
63
+ end
64
+ options.merge!({ :safe => safety })
49
65
  end
50
66
  end
51
67
 
@@ -70,6 +86,20 @@ module Mongoid #:nodoc:
70
86
  def safely(safety = true)
71
87
  tap { Threaded.safety_options = safety }
72
88
  end
89
+
90
+ # Execute the following class-level persistence operation without safe mode.
91
+ # Allows per-request overriding of safe mode when the persist_in_safe_mode
92
+ # config option is turned on.
93
+ #
94
+ # @example Upsert in safe mode.
95
+ # Person.unsafely.create(:name => "John")
96
+ #
97
+ # @return [ Proxy ] The safety proxy.
98
+ #
99
+ # @since 2.3.0
100
+ def unsafely
101
+ tap { Threaded.safety_options = false }
102
+ end
73
103
  end
74
104
  end
75
105
  end
@@ -5,11 +5,9 @@ module Mongoid # :nodoc:
5
5
  # and XML serialization.
6
6
  module Serialization
7
7
  extend ActiveSupport::Concern
8
- include ActiveModel::Serialization
9
8
 
10
- # Gets the document as a serializable hash, used by ActiveModel's JSON and
11
- # XML serializers. This override is just to be able to pass the :include
12
- # and :except options to get associations in the hash.
9
+ # Gets the document as a serializable hash, used by ActiveModel's JSON
10
+ # serializer.
13
11
  #
14
12
  # @example Get the serializable hash.
15
13
  # document.serializable_hash
@@ -19,16 +17,32 @@ module Mongoid # :nodoc:
19
17
  #
20
18
  # @param [ Hash ] options The options to pass.
21
19
  #
22
- # @option options [ Symbol ] :include What relations to include
20
+ # @option options [ Symbol ] :include What relations to include.
23
21
  # @option options [ Symbol ] :only Limit the fields to only these.
24
22
  # @option options [ Symbol ] :except Dont include these fields.
23
+ # @option options [ Symbol ] :methods What methods to include.
25
24
  #
26
25
  # @return [ Hash ] The document, ready to be serialized.
27
26
  #
28
27
  # @since 2.0.0.rc.6
29
28
  def serializable_hash(options = nil)
30
29
  options ||= {}
31
- super(options).tap do |attrs|
30
+
31
+ only = Array.wrap(options[:only]).map(&:to_s)
32
+ except = Array.wrap(options[:except]).map(&:to_s)
33
+
34
+ except |= ['_type']
35
+
36
+ field_names = fields.keys.map { |field| field.to_s }
37
+ attribute_names = (attributes.keys + field_names).sort
38
+ if only.any?
39
+ attribute_names &= only
40
+ elsif except.any?
41
+ attribute_names -= except
42
+ end
43
+
44
+ method_names = Array.wrap(options[:methods]).map { |n| n.to_s if respond_to?(n.to_s) }.compact
45
+ Hash[(attribute_names + method_names).map { |n| [n, send(n)] }].tap do |attrs|
32
46
  serialize_relations(attrs, options) if options[:include]
33
47
  end
34
48
  end
@@ -178,6 +178,17 @@ module Mongoid #:nodoc:
178
178
  Thread.current[:"[mongoid]:safety-options"] = nil
179
179
  end
180
180
 
181
+ # Clear out all options set on a one-time basis.
182
+ #
183
+ # @example Clear out the options.
184
+ # Threaded.clear_options!
185
+ #
186
+ # @since 2.3.0
187
+ def clear_options!
188
+ clear_safety_options!
189
+ self.timeless = false
190
+ end
191
+
181
192
  # Exit the assigning block.
182
193
  #
183
194
  # @example Exit the assigning block.
@@ -314,6 +325,30 @@ module Mongoid #:nodoc:
314
325
  Thread.current[:"[mongoid]:scope-stack"] ||= {}
315
326
  end
316
327
 
328
+ # Get the value of the one-off timeless call.
329
+ #
330
+ # @example Get the timeless value.
331
+ # Threaded.timeless
332
+ #
333
+ # @return [ true, false ] The timeless setting.
334
+ #
335
+ # @since 2.3.0
336
+ def timeless
337
+ !!Thread.current[:"[mongoid]:timeless"]
338
+ end
339
+
340
+ # Set the value of the one-off timeless call.
341
+ #
342
+ # @example Set the timeless value.
343
+ # Threaded.timeless = true
344
+ #
345
+ # @param [ true, false ] value The value.
346
+ #
347
+ # @since 2.3.0
348
+ def timeless=(value)
349
+ Thread.current[:"[mongoid]:timeless"] = value
350
+ end
351
+
317
352
  # Get the update consumer from the current thread.
318
353
  #
319
354
  # @example Get the update consumer.
@@ -340,6 +375,18 @@ module Mongoid #:nodoc:
340
375
  Thread.current[:"[mongoid][#{klass}]:update-consumer"] = consumer
341
376
  end
342
377
 
378
+ # Is the current thread setting timestamps?
379
+ #
380
+ # @example Is the current thread timestamping?
381
+ # Threaded.timestamping?
382
+ #
383
+ # @return [ true, false ] If timestamps can be applied.
384
+ #
385
+ # @since 2.3.0
386
+ def timestamping?
387
+ !timeless
388
+ end
389
+
343
390
  # Is the document validated on the current thread?
344
391
  #
345
392
  # @example Is the document validated?
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "mongoid/timestamps/created"
3
3
  require "mongoid/timestamps/updated"
4
+ require "mongoid/timestamps/timeless"
4
5
 
5
6
  module Mongoid #:nodoc:
6
7
 
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
-
4
3
  module Timestamps
5
4
  # This module handles the behaviour for setting up document created at
6
5
  # timestamp.
@@ -9,13 +8,7 @@ module Mongoid #:nodoc:
9
8
 
10
9
  included do
11
10
  field :created_at, :type => Time
12
-
13
- set_callback :create, :before, :set_created_at
14
-
15
- unless methods.include? 'record_timestamps'
16
- class_attribute :record_timestamps
17
- self.record_timestamps = true
18
- end
11
+ set_callback :create, :before, :set_created_at, :if => :timestamping?
19
12
  end
20
13
 
21
14
  # Update the created_at field on the Document to the current time. This is
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Timestamps
4
+
5
+ # This module adds behaviour for turning off timestamping in single or
6
+ # multiple calls.
7
+ module Timeless
8
+ extend ActiveSupport::Concern
9
+
10
+ # Begin an execution that should skip timestamping.
11
+ #
12
+ # @example Save a document but don't timestamp.
13
+ # person.timeless.save
14
+ #
15
+ # @return [ Document ] The document this was called on.
16
+ #
17
+ # @since 2.3.0
18
+ def timeless
19
+ tap { Threaded.timeless = true }
20
+ end
21
+
22
+ # Are we currently timestamping?
23
+ #
24
+ # @example Should timestamps be applied?
25
+ # person.timestamping?
26
+ #
27
+ # @return [ true, false ] If the current thread is timestamping.
28
+ #
29
+ # @since 2.3.0
30
+ def timestamping?
31
+ Threaded.timestamping?
32
+ end
33
+
34
+ module ClassMethods #:nodoc
35
+
36
+ # Begin an execution that should skip timestamping.
37
+ #
38
+ # @example Create a document but don't timestamp.
39
+ # Person.timeless.create(:title => "Sir")
40
+ #
41
+ # @return [ Class ] The class this was called on.
42
+ #
43
+ # @since 2.3.0
44
+ def timeless
45
+ tap { Threaded.timeless = true }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end