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
data/Rakefile CHANGED
@@ -24,10 +24,6 @@ task :release => :build do
24
24
  system "gem push mongoid-#{Mongoid::VERSION}.gem"
25
25
  end
26
26
 
27
- RSpec::Core::RakeTask.new(:spec) do |spec|
28
- spec.pattern = "spec/**/*_spec.rb"
29
- end
30
-
31
27
  RSpec::Core::RakeTask.new("spec:unit") do |spec|
32
28
  spec.pattern = "spec/unit/**/*_spec.rb"
33
29
  end
@@ -49,4 +45,5 @@ RDoc::Task.new do |rdoc|
49
45
  rdoc.rdoc_files.include("lib/**/*.rb")
50
46
  end
51
47
 
52
- task :default => [ "spec:functional", "spec:unit" ]
48
+ task :spec => [ "spec:functional", "spec:unit" ]
49
+ task :default => :spec
@@ -94,7 +94,7 @@ I18n.load_path << File.join(File.dirname(__FILE__), "config", "locales", "en.yml
94
94
  module Mongoid #:nodoc
95
95
  extend self
96
96
 
97
- MONGODB_VERSION = "1.8.0"
97
+ MONGODB_VERSION = "2.0.0"
98
98
 
99
99
  # Sets the Mongoid configuration options. Best used by passing a block.
100
100
  #
@@ -6,6 +6,7 @@ module Mongoid #:nodoc:
6
6
  # This module contains the logic for handling the internal attributes hash,
7
7
  # and how to get and set values.
8
8
  module Attributes
9
+ extend ActiveSupport::Concern
9
10
  include Processing
10
11
 
11
12
  attr_reader :attributes
@@ -56,7 +57,7 @@ module Mongoid #:nodoc:
56
57
  #
57
58
  # @since 1.0.0
58
59
  def remove_attribute(name)
59
- assigning do
60
+ _assigning do
60
61
  access = name.to_s
61
62
  attribute_will_change!(access)
62
63
  attributes.delete(access)
@@ -95,18 +96,46 @@ module Mongoid #:nodoc:
95
96
  #
96
97
  # @since 1.0.0
97
98
  def write_attribute(name, value)
98
- assigning do
99
+ _assigning do
99
100
  access = name.to_s
101
+ localized = fields[access].try(:localized?)
100
102
  typed_value_for(access, value).tap do |value|
101
103
  unless attributes[access] == value || attribute_changed?(access)
102
104
  attribute_will_change!(access)
103
105
  end
104
- attributes[access] = value
106
+ if localized
107
+ (attributes[access] ||= {}).merge!(value)
108
+ else
109
+ attributes[access] = value
110
+ end
105
111
  end
106
112
  end
107
113
  end
108
114
  alias :[]= :write_attribute
109
115
 
116
+ # Allows you to set all the attributes for a particular mass-assignment security role
117
+ # by passing in a hash of attributes with keys matching the attribute names
118
+ # (which again matches the column names) and the role name using the :as option.
119
+ # To bypass mass-assignment security you can use the :without_protection => true option.
120
+ #
121
+ # @example Assign the attributes.
122
+ # person.assign_attributes(:title => "Mr.")
123
+ #
124
+ # @example Assign the attributes (with a role).
125
+ # person.assign_attributes({ :title => "Mr." }, :as => :admin)
126
+ #
127
+ # @param [ Hash ] attrs The new attributes to set.
128
+ # @param [ Hash ] options Supported options: :without_protection, :as
129
+ #
130
+ # @since 2.2.1
131
+ def assign_attributes(attrs = nil, options = {})
132
+ _assigning do
133
+ process(attrs, options[:as] || :default, !options[:without_protection]) do |document|
134
+ document.identify if new? && id.blank?
135
+ end
136
+ end
137
+ end
138
+
110
139
  # Writes the supplied attributes hash to the document. This will only
111
140
  # overwrite existing attributes if they are present in the new +Hash+, all
112
141
  # others will be preserved.
@@ -122,11 +151,7 @@ module Mongoid #:nodoc:
122
151
  #
123
152
  # @since 1.0.0
124
153
  def write_attributes(attrs = nil, guard_protected_attributes = true)
125
- assigning do
126
- process(attrs, guard_protected_attributes) do |document|
127
- document.identify if new? && id.blank?
128
- end
129
- end
154
+ assign_attributes(attrs, :without_protection => !guard_protected_attributes)
130
155
  end
131
156
  alias :attributes= :write_attributes
132
157
 
@@ -135,18 +160,16 @@ module Mongoid #:nodoc:
135
160
  # Set any missing default values in the attributes.
136
161
  #
137
162
  # @example Get the raw attributes after defaults have been applied.
138
- # person.apply_default_attributes
163
+ # person.apply_defaults
139
164
  #
140
165
  # @return [ Hash ] The raw attributes.
141
166
  #
142
167
  # @since 2.0.0.rc.8
143
- def apply_default_attributes
144
- (@attributes ||= {}).tap do |attrs|
145
- defaults.each do |name|
146
- unless attrs.has_key?(name)
147
- if field = fields[name]
148
- attrs[name] = field.eval_default(self)
149
- end
168
+ def apply_defaults
169
+ defaults.each do |name|
170
+ unless attributes.has_key?(name)
171
+ if field = fields[name]
172
+ attributes[name] = field.eval_default(self)
150
173
  end
151
174
  end
152
175
  end
@@ -157,14 +180,14 @@ module Mongoid #:nodoc:
157
180
  # be in a valid state.
158
181
  #
159
182
  # @example Execute the assignment.
160
- # assigning do
183
+ # _assigning do
161
184
  # person.attributes = { :addresses => [ address ] }
162
185
  # end
163
186
  #
164
187
  # @return [ Object ] The yielded value.
165
188
  #
166
189
  # @since 2.2.0
167
- def assigning
190
+ def _assigning
168
191
  begin
169
192
  Threaded.begin_assign
170
193
  yield
@@ -201,5 +224,32 @@ module Mongoid #:nodoc:
201
224
  def typed_value_for(key, value)
202
225
  fields.has_key?(key) ? fields[key].serialize(value) : value
203
226
  end
227
+
228
+ module ClassMethods #:nodoc:
229
+
230
+ # Alias the provided name to the original field. This will provide an
231
+ # aliased getter, setter, existance check, and all dirty attribute
232
+ # methods.
233
+ #
234
+ # @example Alias the attribute.
235
+ # class Product
236
+ # include Mongoid::Document
237
+ # field :price, :type => Float
238
+ # alias_attribute :cost, :price
239
+ # end
240
+ #
241
+ # @param [ Symbol ] name The new name.
242
+ # @param [ Symbol ] original The original name.
243
+ #
244
+ # @since 2.3.0
245
+ def alias_attribute(name, original)
246
+ class_eval <<-RUBY
247
+ alias :#{name} :#{original}
248
+ alias :#{name}= :#{original}=
249
+ alias :#{name}? :#{original}?
250
+ RUBY
251
+ super
252
+ end
253
+ end
204
254
  end
205
255
  end
@@ -14,12 +14,13 @@ module Mongoid #:nodoc:
14
14
  # person.process(:title => "sir", :age => 40)
15
15
  #
16
16
  # @param [ Hash ] attrs The attributes to set.
17
+ # @param [ Symbol ] role A role for scoped mass assignment.
17
18
  # @param [ Boolean ] guard_protected_attributes False to skip mass assignment protection.
18
19
  #
19
20
  # @since 2.0.0.rc.7
20
- def process(attrs = nil, guard_protected_attributes = true)
21
+ def process(attrs = nil, role = :default, guard_protected_attributes = true)
21
22
  attrs ||= {}
22
- attrs = sanitize_for_mass_assignment(attrs) if guard_protected_attributes
23
+ attrs = sanitize_for_mass_assignment(attrs, role) if guard_protected_attributes
23
24
  attrs.each_pair do |key, value|
24
25
  next if pending_attribute?(key, value)
25
26
  process_attribute(key, value)
@@ -37,7 +38,7 @@ module Mongoid #:nodoc:
37
38
  # @example Is the attribute pending?
38
39
  # document.pending_attribute?(:name, "Durran")
39
40
  #
40
- # @param [ Synbol ] key The name of the attribute.
41
+ # @param [ Symbol ] key The name of the attribute.
41
42
  # @param [ Object ] value The value of the attribute.
42
43
  #
43
44
  # @return [ true, false ] True if pending, false if not.
@@ -21,5 +21,107 @@ module Mongoid #:nodoc:
21
21
  define_model_callbacks :initialize, :only => :after
22
22
  define_model_callbacks :create, :destroy, :save, :update
23
23
  end
24
+
25
+ # Run the callbacks for the document. This overrides active support's
26
+ # functionality to cascade callbacks to embedded documents that have been
27
+ # flagged as such.
28
+ #
29
+ # @example Run the callbacks.
30
+ # run_callbacks :save do
31
+ # save!
32
+ # end
33
+ #
34
+ # @param [ Symbol ] kind The type of callback to execute.
35
+ # @param [ Array ] *args Any options.
36
+ #
37
+ # @return [ Document ] The document
38
+ #
39
+ # @since 2.3.0
40
+ def run_callbacks(kind, *args, &block)
41
+ run_cascading_callbacks(cascadable_children(kind), kind, *args) do
42
+ super(kind, *args, &block)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Execute the callbacks, including all children that have cascade callbacks
49
+ # set to true.
50
+ #
51
+ # @example Run the cascading callbacks.
52
+ # document.run_cascading_callbacks([], :update)
53
+ #
54
+ # @param [ Array<Document> ] children The cascading children.
55
+ # @param [ Symbol ] kind The callback type.
56
+ # @param [ Array ] args The options.
57
+ #
58
+ # @since 2.3.0
59
+ def run_cascading_callbacks(children, kind, *args, &block)
60
+ if child = children.pop
61
+ run_cascading_callbacks(children, kind, *args) do
62
+ child.run_callbacks(child_callback_type(kind, child), *args) do
63
+ block.call
64
+ end
65
+ end
66
+ else
67
+ block.call
68
+ end
69
+ end
70
+
71
+ # Get all the child embedded documents that are flagged as cascadable.
72
+ #
73
+ # @example Get all the cascading children.
74
+ # document.cascadable_children(:update)
75
+ #
76
+ # @param [ Symbol ] kind The type of callback.
77
+ #
78
+ # @return [ Array<Document> ] The children.
79
+ #
80
+ # @since 2.3.0
81
+ def cascadable_children(kind)
82
+ [].tap do |children|
83
+ relations.each_pair do |name, metadata|
84
+ next unless metadata.cascading_callbacks?
85
+ child = send(name)
86
+ Array.wrap(child).each do |doc|
87
+ children.push(doc) if cascadable_child?(kind, doc)
88
+ children.concat(doc.send(:cascadable_children, kind))
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Determine if the child should fire the callback.
95
+ #
96
+ # @example Should the child fire the callback?
97
+ # document.cascadable_child?(:update, doc)
98
+ #
99
+ # @param [ Symbol ] kind The type of callback.
100
+ # @param [ Document ] child The child document.
101
+ #
102
+ # @return [ true, false ] If the child should fire the callback.
103
+ #
104
+ # @since 2.3.0
105
+ def cascadable_child?(kind, child)
106
+ [ :create, :destroy ].include?(kind) || child.changed? || child.new_record?
107
+ end
108
+
109
+ # Get the name of the callback that the child should fire. This changes
110
+ # depending on whether or not the child is new. A persisted parent with a
111
+ # new child would fire :update from the parent, but needs to fire :create
112
+ # on the child.
113
+ #
114
+ # @example Get the callback type.
115
+ # document.child_callback_type(:update, doc)
116
+ #
117
+ # @param [ Symbol ] kind The type of callback.
118
+ # @param [ Document ] child The child document
119
+ #
120
+ # @return [ Symbol ] The name of the callback.
121
+ #
122
+ # @since 2.3.0
123
+ def child_callback_type(kind, child)
124
+ kind == :update && child.new_record? ? :create : kind
125
+ end
24
126
  end
25
127
  end
@@ -142,7 +142,7 @@ module Mongoid #:nodoc
142
142
  #
143
143
  # @since 2.0.0
144
144
  def update(selector, document, options = {})
145
- updater = Threaded.update_consumer(name)
145
+ updater = Threaded.update_consumer(klass)
146
146
  if updater
147
147
  updater.consume(selector, document, options)
148
148
  else
@@ -17,11 +17,11 @@ module Mongoid #:nodoc
17
17
  include ActiveModel::Serializers::JSON
18
18
  include ActiveModel::Serializers::Xml
19
19
  include Mongoid::Atomic
20
+ include Mongoid::Dirty
20
21
  include Mongoid::Attributes
21
22
  include Mongoid::Collections
22
23
  include Mongoid::Copyable
23
24
  include Mongoid::DefaultScope
24
- include Mongoid::Dirty
25
25
  include Mongoid::Extras
26
26
  include Mongoid::Fields
27
27
  include Mongoid::Hierarchy
@@ -38,6 +38,7 @@ module Mongoid #:nodoc
38
38
  include Mongoid::Serialization
39
39
  include Mongoid::Sharding
40
40
  include Mongoid::State
41
+ include Mongoid::Timestamps::Timeless
41
42
  include Mongoid::Validations
42
43
  include Mongoid::Callbacks
43
44
  include Mongoid::MultiDatabase
@@ -210,18 +210,6 @@ module Mongoid #:nodoc:
210
210
 
211
211
  protected
212
212
 
213
- # Get the root class collection name.
214
- #
215
- # @example Get the root class collection name.
216
- # context.collection_name
217
- #
218
- # @return [ String ] The name of the collection.
219
- #
220
- # @since 2.4.3
221
- def collection_name
222
- root ? root.collection_name : nil
223
- end
224
-
225
213
  # Filters the documents against the criteria's selector
226
214
  #
227
215
  # @example Filter the documents.
@@ -263,22 +251,10 @@ module Mongoid #:nodoc:
263
251
  documents
264
252
  end
265
253
 
266
- # Get the root document for the enumerable.
267
- #
268
- # @example Get the root document.
269
- # context.root
270
- #
271
- # @return [ Document ] The root.
272
254
  def root
273
255
  @root ||= documents.first.try(:_root)
274
256
  end
275
257
 
276
- # Get the root class for the enumerable.
277
- #
278
- # @example Get the root class.
279
- # context.root_class
280
- #
281
- # @return [ Class ] The root class.
282
258
  def root_class
283
259
  @root_class ||= root ? root.class : nil
284
260
  end
@@ -330,7 +330,7 @@ module Mongoid #:nodoc:
330
330
  { "$set" => attributes },
331
331
  Safety.merge_safety_options(:multi => true)
332
332
  ).tap do
333
- Threaded.clear_safety_options!
333
+ Threaded.clear_options!
334
334
  end
335
335
  end
336
336
  alias :update :update_all
@@ -371,7 +371,7 @@ module Mongoid #:nodoc:
371
371
  :reduce => reduce.gsub("[field]", field)
372
372
  )
373
373
  value = collection.empty? ? nil : collection.first[start.to_s]
374
- value ? (value.nan? ? nil : value) : value
374
+ value && value.try(:nan?) ? nil : value
375
375
  end
376
376
 
377
377
  # Filters the field list. If no fields have been supplied, then it will be
@@ -38,7 +38,9 @@ module Mongoid #:nodoc:
38
38
  instance_variable_set(name, value ? value.dup : nil)
39
39
  end
40
40
  attributes.delete("_id")
41
- attributes.delete("versions")
41
+ if attributes.delete("versions")
42
+ attributes["version"] = 1
43
+ end
42
44
  @new_record = true
43
45
  identify
44
46
  end
@@ -153,6 +153,19 @@ module Mongoid #:nodoc:
153
153
  context.count > 0
154
154
  end
155
155
 
156
+ # Extract a single id from the provided criteria. Could be in an $and
157
+ # query or a straight _id query.
158
+ #
159
+ # @example Extract the id.
160
+ # criteria.extract_id
161
+ #
162
+ # @return [ Object ] The id.
163
+ #
164
+ # @since 2.3.0
165
+ def extract_id
166
+ selector[:_id]
167
+ end
168
+
156
169
  # When freezing a criteria we need to initialize the context first
157
170
  # otherwise the setting of the context on attempted iteration will raise a
158
171
  # runtime error.
@@ -383,19 +396,14 @@ module Mongoid #:nodoc:
383
396
  clone.tap do |crit|
384
397
  converted = BSON::ObjectId.convert(klass, attributes || {})
385
398
  converted.each_pair do |key, value|
386
- existing = crit.selector[key]
387
- unless existing
399
+ unless crit.selector[key]
388
400
  crit.selector[key] = { operator => value }
389
401
  else
390
- if existing.respond_to?(:merge)
391
- if existing.has_key?(operator)
392
- new_value = existing.values.first.send(combine, value)
393
- crit.selector[key] = { operator => new_value }
394
- else
395
- crit.selector[key][operator] = value
396
- end
402
+ if crit.selector[key].has_key?(operator)
403
+ new_value = crit.selector[key].values.first.send(combine, value)
404
+ crit.selector[key] = { operator => new_value }
397
405
  else
398
- crit.selector[key] = { operator => value }
406
+ crit.selector[key][operator] = value
399
407
  end
400
408
  end
401
409
  end