mongoid 2.2.6 → 2.3.0

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