mongoid 0.10.6 → 0.11.0

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