mongoid 0.10.6 → 0.11.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 (42) hide show
  1. data/Rakefile +3 -3
  2. data/VERSION +1 -1
  3. data/lib/mongoid.rb +2 -2
  4. data/lib/mongoid/associations.rb +1 -1
  5. data/lib/mongoid/associations/belongs_to.rb +1 -1
  6. data/lib/mongoid/associations/has_many.rb +8 -6
  7. data/lib/mongoid/associations/has_one.rb +6 -5
  8. data/lib/mongoid/commands.rb +94 -41
  9. data/lib/mongoid/commands/delete_all.rb +2 -3
  10. data/lib/mongoid/commands/deletion.rb +1 -1
  11. data/lib/mongoid/commands/destroy_all.rb +2 -0
  12. data/lib/mongoid/commands/save.rb +1 -1
  13. data/lib/mongoid/criteria.rb +1 -1
  14. data/lib/mongoid/document.rb +43 -32
  15. data/lib/mongoid/extensions.rb +3 -3
  16. data/lib/mongoid/extensions/hash/assimilation.rb +2 -3
  17. data/lib/mongoid/extensions/string/inflections.rb +1 -0
  18. data/mongoid.gemspec +13 -9
  19. data/perf/benchmark.rb +8 -4
  20. data/spec/integration/mongoid/commands_spec.rb +103 -0
  21. data/spec/integration/mongoid/document_spec.rb +13 -1
  22. data/spec/integration/mongoid/inheritance_spec.rb +105 -0
  23. data/spec/spec_helper.rb +24 -2
  24. data/spec/unit/mongoid/associations/belongs_to_spec.rb +3 -3
  25. data/spec/unit/mongoid/associations/has_many_spec.rb +92 -31
  26. data/spec/unit/mongoid/associations/has_one_spec.rb +57 -4
  27. data/spec/unit/mongoid/associations_spec.rb +4 -4
  28. data/spec/unit/mongoid/attributes_spec.rb +2 -2
  29. data/spec/unit/mongoid/commands/delete_all_spec.rb +4 -3
  30. data/spec/unit/mongoid/commands/delete_spec.rb +1 -1
  31. data/spec/unit/mongoid/commands/destroy_all_spec.rb +3 -2
  32. data/spec/unit/mongoid/commands/destroy_spec.rb +1 -1
  33. data/spec/unit/mongoid/commands/save_spec.rb +3 -3
  34. data/spec/unit/mongoid/commands_spec.rb +6 -6
  35. data/spec/unit/mongoid/criteria_spec.rb +45 -36
  36. data/spec/unit/mongoid/document_spec.rb +198 -29
  37. data/spec/unit/mongoid/extensions/array/conversions_spec.rb +2 -2
  38. data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +33 -8
  39. data/spec/unit/mongoid/extensions/object/conversions_spec.rb +2 -2
  40. data/spec/unit/mongoid/finders_spec.rb +1 -1
  41. data/spec/unit/mongoid/timestamps_spec.rb +1 -1
  42. metadata +9 -5
data/Rakefile CHANGED
@@ -9,12 +9,12 @@ begin
9
9
  gem.name = "mongoid"
10
10
  gem.summary = "ODM framework for MongoDB"
11
11
  gem.email = "durran@gmail.com"
12
- gem.homepage = "http://github.com/durran/mongoid"
12
+ gem.homepage = "http://mongoid.org"
13
13
  gem.authors = ["Durran Jordan"]
14
14
 
15
15
  gem.add_dependency("activesupport", ">= 2.2.2")
16
- gem.add_dependency("mongo", ">= 0.18.1")
17
- gem.add_dependency("mongo_ext", ">= 0.18.1")
16
+ gem.add_dependency("mongo", ">= 0.18.2")
17
+ gem.add_dependency("mongo_ext", ">= 0.18.2")
18
18
  gem.add_dependency("durran-validatable", ">= 1.8.4")
19
19
  gem.add_dependency("leshill-will_paginate", ">= 2.3.11")
20
20
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.6
1
+ 0.11.0
@@ -22,8 +22,8 @@
22
22
  require "rubygems"
23
23
 
24
24
  gem "activesupport", ">= 2.2.2"
25
- gem "mongo", ">= 0.18.1"
26
- gem "mongo_ext", ">= 0.18.1"
25
+ gem "mongo", ">= 0.18.2"
26
+ gem "mongo_ext", ">= 0.18.2"
27
27
  gem "durran-validatable", ">= 1.8.4"
28
28
  gem "leshill-will_paginate", ">= 2.3.11"
29
29
 
@@ -60,7 +60,7 @@ module Mongoid # :nodoc:
60
60
  unless options.has_key?(:inverse_of)
61
61
  raise Errors::InvalidOptions.new("Options for belongs_to association must include :inverse_of")
62
62
  end
63
- @embedded = true
63
+ self.embedded = true
64
64
  add_association(
65
65
  Associations::BelongsTo,
66
66
  Associations::Options.new(options.merge(:name => name))
@@ -43,7 +43,7 @@ module Mongoid #:nodoc:
43
43
  # document: The parent +Document+
44
44
  # options: The association options
45
45
  def instantiate(document, options)
46
- parent = document.parent
46
+ parent = document._parent
47
47
  parent.nil? ? nil : new(parent, options)
48
48
  end
49
49
 
@@ -37,8 +37,8 @@ module Mongoid #:nodoc:
37
37
  # association, and the attributes will be passed into the constructor.
38
38
  #
39
39
  # Returns the newly created object.
40
- def build(attributes = {})
41
- object = @klass.instantiate(attributes)
40
+ def build(attrs = {}, type = nil)
41
+ object = type ? type.instantiate(attrs) : @klass.instantiate(attrs)
42
42
  push(object)
43
43
  object
44
44
  end
@@ -49,9 +49,10 @@ module Mongoid #:nodoc:
49
49
  # the new object will then be saved.
50
50
  #
51
51
  # Returns the newly created object.
52
- def create(attributes)
53
- object = build(attributes)
52
+ def create(attrs = {}, type = nil)
53
+ object = build(attrs, type)
54
54
  object.save
55
+ object
55
56
  end
56
57
 
57
58
  # Finds a document in this association.
@@ -72,8 +73,9 @@ module Mongoid #:nodoc:
72
73
  def initialize(document, options)
73
74
  @parent, @association_name, @klass, @options = document, options.name, options.klass, options
74
75
  attributes = document.attributes[@association_name]
75
- @documents = attributes ? attributes.collect do |attribute|
76
- child = @klass.instantiate(attribute)
76
+ @documents = attributes ? attributes.collect do |attrs|
77
+ type = attrs[:_type]
78
+ child = type ? type.constantize.instantiate(attrs) : @klass.instantiate(attrs)
77
79
  child.parentize(@parent, @association_name)
78
80
  child
79
81
  end : []
@@ -7,14 +7,14 @@ module Mongoid #:nodoc:
7
7
  attr_reader :association_name, :document, :parent, :options
8
8
 
9
9
  # Build a new object for the association.
10
- def build(attributes = {})
11
- @document = attributes.assimilate(@parent, @options)
10
+ def build(attrs = {}, type = nil)
11
+ @document = attrs.assimilate(@parent, @options, type)
12
12
  self
13
13
  end
14
14
 
15
15
  # Create a new object for the association and save it.
16
- def create(attributes)
17
- build(attributes)
16
+ def create(attrs = {}, type = nil)
17
+ build(attrs, type)
18
18
  @document.save
19
19
  self
20
20
  end
@@ -34,7 +34,8 @@ module Mongoid #:nodoc:
34
34
  def initialize(document, attributes, options)
35
35
  @parent, @options, @association_name = document, options, options.name
36
36
  unless attributes.nil?
37
- @document = attributes.assimilate(@parent, @options)
37
+ klass = attributes[:_type] ? attributes[:_type].constantize : nil
38
+ @document = attributes.assimilate(@parent, @options, klass)
38
39
  end
39
40
  end
40
41
 
@@ -10,21 +10,7 @@ require "mongoid/commands/save"
10
10
  module Mongoid #:nodoc:
11
11
 
12
12
  # This module is included in the +Document+ to provide all the persistence
13
- # methods required on the +Document+ object and class. The following methods
14
- # are provided:
15
- #
16
- # <tt>create</tt>,
17
- # <tt>create!</tt>,
18
- # <tt>delete</tt>,
19
- # <tt>delete_all</tt>,
20
- # <tt>destroy</tt>,
21
- # <tt>destroy_all</tt>,
22
- # <tt>save</tt>,
23
- # <tt>save!</tt>,
24
- # <tt>update_attributes</tt>,
25
- # <tt>update_attributes!</tt>
26
- #
27
- # These methods will delegate to their respective commands.
13
+ # methods required on the +Document+ object and class.
28
14
  module Commands
29
15
  def self.included(base)
30
16
  base.class_eval do
@@ -35,41 +21,79 @@ module Mongoid #:nodoc:
35
21
 
36
22
  module InstanceMethods
37
23
 
38
- # Delete the +Document+ from the database. Delegates to the Delete
39
- # command.
24
+ # Delete the +Document+ from the database. This method is an optimized
25
+ # delete that does not force any callbacks.
26
+ #
27
+ # Example:
28
+ #
29
+ # <tt>document.delete</tt>
30
+ #
31
+ # Returns: true unless an error occurs.
40
32
  def delete
41
33
  Delete.execute(self)
42
34
  end
43
35
 
44
- # Destroy the +Document+. Delegates to the Destroy command.
36
+ # Destroy the +Document+. This will delete the document from the database
37
+ # and run the before and after destroy callbacks.
38
+ #
39
+ # Example:
40
+ #
41
+ # <tt>document.destroy</tt>
42
+ #
43
+ # Returns: true unless an error occurs.
45
44
  def destroy
46
45
  Destroy.execute(self)
47
46
  end
48
47
 
49
- # Save the +Document+. Delegates to the Save command.
48
+ # Save the +Document+. If the document is new, then the before and after
49
+ # create callbacks will get executed as well as the save callbacks.
50
+ # Otherwise only the save callbacks will run.
51
+ #
52
+ # Options:
53
+ #
54
+ # validate: Run validations or not. Defaults to true.
55
+ #
56
+ # Example:
57
+ #
58
+ # <tt>document.save # save with validations</tt>
59
+ # <tt>document.save(false) # save without validations</tt>
60
+ #
61
+ # Returns: true if validation passes, false if not.
50
62
  def save(validate = true)
51
- new_record? ? Create.execute(self, validate) : Save.execute(self, validate)
63
+ run_callbacks(:before_create) if new_record?
64
+ saved = Save.execute(self, validate)
65
+ run_callbacks(:after_create) if new_record?
66
+ saved
52
67
  end
53
68
 
54
- # Save the +Document+. Delegates to the Save command. If the command
55
- # returns false then a +ValidationError+ will be raised.
69
+ # Save the +Document+, dangerously. Before and after save callbacks will
70
+ # get run. If validation fails an error will get raised.
71
+ #
72
+ # Example:
73
+ #
74
+ # <tt>document.save!</tt>
75
+ #
76
+ # Returns: true if validation passes
56
77
  def save!
57
- if new_record?
58
- return Create.execute(self, true) || (raise Errors::Validations.new(self.errors))
59
- else
60
- return Save.execute(self, true) || (raise Errors::Validations.new(self.errors))
61
- end
78
+ return Save.execute(self, true) || (raise Errors::Validations.new(self.errors))
62
79
  end
63
80
 
64
- # Update the attributes of the +Document+. Will call save after the
65
- # attributes have been updated.
81
+ # Update the document attributes and persist the document to the
82
+ # database. Will delegate to save with all callbacks.
83
+ #
84
+ # Example:
85
+ #
86
+ # <tt>document.update_attributes(:title => "Test")</tt>
66
87
  def update_attributes(attrs = {})
67
88
  write_attributes(attrs); save
68
89
  end
69
90
 
70
- # Update the attributes of the +Document+. Will call save! after the
71
- # attributes have been updated, causing a +ValidationError+ if the
72
- # +Document+ failed validation.
91
+ # Update the document attributes and persist the document to the
92
+ # database. Will delegate to save!
93
+ #
94
+ # Example:
95
+ #
96
+ # <tt>document.update_attributes!(:title => "Test")</tt>
73
97
  def update_attributes!(attrs = {})
74
98
  write_attributes(attrs); save!
75
99
  end
@@ -78,29 +102,58 @@ module Mongoid #:nodoc:
78
102
 
79
103
  module ClassMethods
80
104
 
81
- # Create a new +Document+ with the supplied attributes. Will delegate to
82
- # the Create command.
105
+ # Create a new +Document+. This will instantiate a new document and save
106
+ # it in a single call. Will always return the document whether save
107
+ # passed or not.
108
+ #
109
+ # Example:
110
+ #
111
+ # <tt>Person.create(:title => "Mr")</tt>
112
+ #
113
+ # Returns: the +Document+.
83
114
  def create(attributes = {})
84
115
  Create.execute(new(attributes))
85
116
  end
86
117
 
87
- # Create a new +Document+ with the supplied attributes. Will delegate to
88
- # the Create command or raise +ValidationError+ if the save failed
89
- # validation.
118
+ # Create a new +Document+. This will instantiate a new document and save
119
+ # it in a single call. Will always return the document whether save
120
+ # passed or not. Will raise an error if validation fails.
121
+ #
122
+ # Example:
123
+ #
124
+ # <tt>Person.create!(:title => "Mr")</tt>
125
+ #
126
+ # Returns: the +Document+.
90
127
  def create!(attributes = {})
91
128
  document = Create.execute(new(attributes))
92
129
  raise Errors::Validations.new(self.errors) unless document.errors.empty?
93
130
  return document
94
131
  end
95
132
 
96
- # Delete all the +Documents+ in the database given the supplied
97
- # conditions.
133
+ # Delete all documents given the supplied conditions. If no conditions
134
+ # are passed, the entire collection will be dropped for performance
135
+ # benefits. Does not fire any callbacks.
136
+ #
137
+ # Example:
138
+ #
139
+ # <tt>Person.delete_all(:conditions => { :title => "Sir" })</tt>
140
+ # <tt>Person.delete_all</tt>
141
+ #
142
+ # Returns: true or raises an error.
98
143
  def delete_all(conditions = {})
99
144
  DeleteAll.execute(self, conditions)
100
145
  end
101
146
 
102
- # Destroy all the +Documents+ in the database given the supplied
103
- # conditions.
147
+ # Delete all documents given the supplied conditions. If no conditions
148
+ # are passed, the entire collection will be dropped for performance
149
+ # benefits. Fires the destroy callbacks if conditions were passed.
150
+ #
151
+ # Example:
152
+ #
153
+ # <tt>Person.destroy_all(:conditions => { :title => "Sir" })</tt>
154
+ # <tt>Person.destroy_all</tt>
155
+ #
156
+ # Returns: true or raises an error.
104
157
  def destroy_all(conditions = {})
105
158
  DestroyAll.execute(self, conditions)
106
159
  end
@@ -14,9 +14,8 @@ module Mongoid #:nodoc:
14
14
  #
15
15
  # <tt>DeleteAll.execute(Person, :conditions => { :field => "value" })</tt>
16
16
  def self.execute(klass, params = {})
17
- params.any? ? klass.find(:all, params).each do
18
- |doc| Delete.execute(doc)
19
- end : klass.collection.drop
17
+ collection = klass.collection
18
+ params.any? ? collection.remove(params[:conditions].merge(:_type => klass.name)) : collection.drop
20
19
  end
21
20
  end
22
21
  end
@@ -5,7 +5,7 @@ module Mongoid #:nodoc
5
5
  # If the +Document+ has a parent, delete it from the parent's attributes,
6
6
  # otherwise delete it from it's collection.
7
7
  def delete(doc)
8
- parent = doc.parent
8
+ parent = doc._parent
9
9
  parent ? parent.remove(doc) : doc.collection.remove(:_id => doc.id)
10
10
  end
11
11
  end
@@ -14,6 +14,8 @@ module Mongoid #:nodoc:
14
14
  #
15
15
  # <tt>DestroyAll.execute(Person, :conditions => { :field => "value" })</tt>
16
16
  def self.execute(klass, params)
17
+ conditions = params[:conditions] || {}
18
+ params[:conditions] = conditions.merge(:_type => klass.name)
17
19
  klass.find(:all, params).each { |doc| Destroy.execute(doc) }
18
20
  end
19
21
  end
@@ -13,7 +13,7 @@ module Mongoid #:nodoc:
13
13
  def self.execute(doc, validate = true)
14
14
  return false if validate && !doc.valid?
15
15
  doc.run_callbacks :before_save
16
- parent = doc.parent
16
+ parent = doc._parent
17
17
  doc.new_record = false
18
18
  parent ? Save.execute(parent) : doc.collection.save(doc.attributes)
19
19
  doc.run_callbacks :after_save
@@ -214,7 +214,7 @@ module Mongoid #:nodoc:
214
214
  # type: One of :all, :first:, or :last
215
215
  # klass: The class to execute on.
216
216
  def initialize(klass)
217
- @selector, @options, @klass = {}, {}, klass
217
+ @selector, @options, @klass = { :_type => klass.name }, {}, klass
218
218
  end
219
219
 
220
220
  # Return the last result for the +Criteria+. Essentially does a find_one on
@@ -10,11 +10,26 @@ module Mongoid #:nodoc:
10
10
  extend ClassMethods
11
11
  extend Finders
12
12
 
13
- attr_accessor :association_name, :parent
13
+ # Set up the class attributes that must be available to all subclasses.
14
+ # These include defaults, fields
15
+ class_inheritable_accessor :defaults, :fields
16
+
17
+ # The same collection is used for the entire class hierarchy.
18
+ cattr_accessor :_collection, :collection_name, :embedded, :primary_key
19
+
20
+ # Set the initial values. Defaults and fields get set to a
21
+ # +HashWithIndifferentAccess+ while the collection name will get set to
22
+ # the demodulized class.
23
+ self.defaults = {}.with_indifferent_access
24
+ self.fields = {}.with_indifferent_access
25
+ self.collection_name ||= self.to_s.demodulize.tableize
26
+
27
+ attr_accessor :association_name, :_parent
14
28
  attr_reader :attributes, :new_record
15
29
 
16
30
  delegate :collection, :defaults, :embedded?, :fields, :primary_key, :to => :klass
17
31
 
32
+ # Define all the callbacks that are accepted by the document.
18
33
  define_callbacks :before_create, :before_destroy, :before_save, :before_update, :before_validation
19
34
  define_callbacks :after_create, :after_destroy, :after_save, :after_update, :after_validation
20
35
  end
@@ -28,23 +43,12 @@ module Mongoid #:nodoc:
28
43
  # Returns: <tt>Mongo::Collection</tt>
29
44
  def collection
30
45
  raise Errors::InvalidCollection.new(self) if embedded?
31
- @collection_name ||= self.to_s.demodulize.tableize
32
- @collection ||= Mongoid.database.collection(@collection_name)
33
- end
34
-
35
- # Set the collection name for the +Document+.
36
- def collection_name(name)
37
- @collection_name = name
38
- end
39
-
40
- # Returns a hash of all the default values
41
- def defaults
42
- @defaults
46
+ self._collection ||= Mongoid.database.collection(self.collection_name)
43
47
  end
44
48
 
45
49
  # return true if the +Document+ is embedded in another +Documnet+.
46
50
  def embedded?
47
- @embedded == true
51
+ self.embedded == true
48
52
  end
49
53
 
50
54
  # Defines all the fields that are accessable on the Document
@@ -64,11 +68,6 @@ module Mongoid #:nodoc:
64
68
  set_default(name, options)
65
69
  end
66
70
 
67
- # Returns all the fields for the Document as a +Hash+ with names as keys.
68
- def fields
69
- @fields
70
- end
71
-
72
71
  # Returns a human readable version of the class.
73
72
  def human_name
74
73
  name.underscore.humanize
@@ -98,13 +97,13 @@ module Mongoid #:nodoc:
98
97
  # web applications and *MUST* be defined on documents that are embedded
99
98
  # in order for proper updates in has_may associations.
100
99
  def key(*fields)
101
- @primary_key = fields
100
+ self.primary_key = fields
102
101
  before_save :generate_key
103
102
  end
104
103
 
105
- # Returns the primary key field of the +Document+
106
- def primary_key
107
- @primary_key
104
+ # Macro for setting the collection name to store in.
105
+ def store_in(name)
106
+ self.collection_name = name.to_s
108
107
  end
109
108
 
110
109
  protected
@@ -112,8 +111,7 @@ module Mongoid #:nodoc:
112
111
  # Define a field attribute for the +Document+.
113
112
  def set_field(name, options = {})
114
113
  meth = options.delete(:as) || name
115
- @fields ||= {}.with_indifferent_access
116
- @fields[name] = Field.new(name.to_s, options)
114
+ fields[name] = Field.new(name.to_s, options)
117
115
  create_accessors(name, meth, options)
118
116
  end
119
117
 
@@ -127,8 +125,7 @@ module Mongoid #:nodoc:
127
125
  # Set up a default value for a field.
128
126
  def set_default(name, options = {})
129
127
  value = options[:default]
130
- @defaults ||= {}.with_indifferent_access
131
- @defaults[name] = value if value
128
+ defaults[name] = value if value
132
129
  end
133
130
 
134
131
  end
@@ -199,7 +196,7 @@ module Mongoid #:nodoc:
199
196
  process(defaults.merge(attrs))
200
197
  @new_record = true if id.nil?
201
198
  document = yield self if block_given?
202
- generate_key; document
199
+ generate_key; generate_type; document
203
200
  end
204
201
 
205
202
  # Returns the class name plus its attributes.
@@ -209,7 +206,7 @@ module Mongoid #:nodoc:
209
206
  end
210
207
 
211
208
  # Returns true is the +Document+ has not been persisted to the database,
212
- # false if it has. This is determined by the instance variable @new_record
209
+ # false if it has. This is determined by the variable @new_record
213
210
  # and NOT if the object has an id.
214
211
  def new_record?
215
212
  @new_record == true
@@ -243,7 +240,7 @@ module Mongoid #:nodoc:
243
240
  #
244
241
  # <tt>address.parentize(person, :addresses)</tt>
245
242
  def parentize(object, association_name)
246
- self.parent = object
243
+ self._parent = object
247
244
  self.association_name = association_name
248
245
  add_observer(object)
249
246
  end
@@ -264,9 +261,9 @@ module Mongoid #:nodoc:
264
261
 
265
262
  # Return the root +Document+ in the object graph. If the current +Document+
266
263
  # is the root object in the graph it will return self.
267
- def root
264
+ def _root
268
265
  object = self
269
- while (object.parent) do object = object.parent; end
266
+ while (object._parent) do object = object._parent; end
270
267
  object || self
271
268
  end
272
269
 
@@ -280,6 +277,16 @@ module Mongoid #:nodoc:
280
277
  id
281
278
  end
282
279
 
280
+ # Returns the object type.
281
+ def _type
282
+ @attributes[:_type]
283
+ end
284
+
285
+ # Set the type.
286
+ def _type=(new_type)
287
+ @attributes[:_type] = new_type
288
+ end
289
+
283
290
  # Observe a notify call from a child +Document+. This will either update
284
291
  # existing attributes on the +Document+ or clear them out for the child if
285
292
  # the clear boolean is provided.
@@ -320,6 +327,10 @@ module Mongoid #:nodoc:
320
327
  end
321
328
  end
322
329
 
330
+ def generate_type
331
+ @attributes[:_type] = self.class.name unless @attributes[:_type]
332
+ end
333
+
323
334
  # Convenience method to get the document's class
324
335
  def klass
325
336
  self.class