mongoid 1.0.6 → 1.1.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 (79) hide show
  1. data/HISTORY +39 -0
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongoid.rb +3 -1
  5. data/lib/mongoid/associations.rb +5 -5
  6. data/lib/mongoid/associations/has_many.rb +8 -2
  7. data/lib/mongoid/associations/has_many_related.rb +10 -4
  8. data/lib/mongoid/attributes.rb +19 -13
  9. data/lib/mongoid/commands.rb +12 -6
  10. data/lib/mongoid/commands/create.rb +2 -2
  11. data/lib/mongoid/commands/delete_all.rb +1 -1
  12. data/lib/mongoid/commands/save.rb +2 -2
  13. data/lib/mongoid/components.rb +1 -0
  14. data/lib/mongoid/contexts.rb +4 -0
  15. data/lib/mongoid/contexts/enumerable.rb +105 -0
  16. data/lib/mongoid/contexts/mongo.rb +228 -0
  17. data/lib/mongoid/contexts/paging.rb +42 -0
  18. data/lib/mongoid/criteria.rb +42 -191
  19. data/lib/mongoid/document.rb +19 -13
  20. data/lib/mongoid/extensions.rb +1 -0
  21. data/lib/mongoid/extensions/array/accessors.rb +3 -1
  22. data/lib/mongoid/extensions/float/conversions.rb +1 -1
  23. data/lib/mongoid/extensions/hash/accessors.rb +1 -1
  24. data/lib/mongoid/extensions/integer/conversions.rb +1 -0
  25. data/lib/mongoid/fields.rb +6 -5
  26. data/lib/mongoid/matchers.rb +36 -0
  27. data/lib/mongoid/matchers/all.rb +11 -0
  28. data/lib/mongoid/matchers/default.rb +20 -0
  29. data/lib/mongoid/matchers/exists.rb +13 -0
  30. data/lib/mongoid/matchers/gt.rb +11 -0
  31. data/lib/mongoid/matchers/gte.rb +11 -0
  32. data/lib/mongoid/matchers/in.rb +11 -0
  33. data/lib/mongoid/matchers/lt.rb +11 -0
  34. data/lib/mongoid/matchers/lte.rb +11 -0
  35. data/lib/mongoid/matchers/ne.rb +11 -0
  36. data/lib/mongoid/matchers/nin.rb +11 -0
  37. data/lib/mongoid/matchers/size.rb +11 -0
  38. data/lib/mongoid/scope.rb +17 -1
  39. data/mongoid.gemspec +51 -5
  40. data/spec/integration/mongoid/associations_spec.rb +67 -5
  41. data/spec/integration/mongoid/attributes_spec.rb +22 -0
  42. data/spec/integration/mongoid/commands_spec.rb +51 -12
  43. data/spec/integration/mongoid/criteria_spec.rb +3 -3
  44. data/spec/integration/mongoid/document_spec.rb +8 -8
  45. data/spec/integration/mongoid/finders_spec.rb +1 -1
  46. data/spec/integration/mongoid/inheritance_spec.rb +6 -0
  47. data/spec/integration/mongoid/named_scope_spec.rb +1 -1
  48. data/spec/spec_helper.rb +47 -6
  49. data/spec/unit/mongoid/associations/has_many_related_spec.rb +42 -0
  50. data/spec/unit/mongoid/associations/has_many_spec.rb +40 -1
  51. data/spec/unit/mongoid/attributes_spec.rb +1 -1
  52. data/spec/unit/mongoid/commands/create_spec.rb +3 -3
  53. data/spec/unit/mongoid/commands/delete_all_spec.rb +2 -2
  54. data/spec/unit/mongoid/commands/save_spec.rb +2 -2
  55. data/spec/unit/mongoid/commands_spec.rb +12 -12
  56. data/spec/unit/mongoid/contexts/enumerable_spec.rb +208 -0
  57. data/spec/unit/mongoid/contexts/mongo_spec.rb +370 -0
  58. data/spec/unit/mongoid/criteria_spec.rb +182 -21
  59. data/spec/unit/mongoid/extensions/array/accessors_spec.rb +9 -9
  60. data/spec/unit/mongoid/extensions/date/conversions_spec.rb +2 -1
  61. data/spec/unit/mongoid/extensions/float/conversions_spec.rb +4 -4
  62. data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +1 -1
  63. data/spec/unit/mongoid/fields_spec.rb +3 -3
  64. data/spec/unit/mongoid/identity_spec.rb +2 -2
  65. data/spec/unit/mongoid/matchers/all_spec.rb +27 -0
  66. data/spec/unit/mongoid/matchers/default_spec.rb +27 -0
  67. data/spec/unit/mongoid/matchers/exists_spec.rb +56 -0
  68. data/spec/unit/mongoid/matchers/gt_spec.rb +39 -0
  69. data/spec/unit/mongoid/matchers/gte_spec.rb +49 -0
  70. data/spec/unit/mongoid/matchers/in_spec.rb +27 -0
  71. data/spec/unit/mongoid/matchers/lt_spec.rb +39 -0
  72. data/spec/unit/mongoid/matchers/lte_spec.rb +49 -0
  73. data/spec/unit/mongoid/matchers/ne_spec.rb +27 -0
  74. data/spec/unit/mongoid/matchers/nin_spec.rb +27 -0
  75. data/spec/unit/mongoid/matchers/size_spec.rb +27 -0
  76. data/spec/unit/mongoid/matchers_spec.rb +329 -0
  77. data/spec/unit/mongoid/scope_spec.rb +70 -0
  78. data/spec/unit/mongoid/timestamps_spec.rb +2 -2
  79. metadata +50 -4
data/HISTORY CHANGED
@@ -1,3 +1,42 @@
1
+ === 1.0.7
2
+ - Nil attributes no longer persist nil values to the
3
+ database - the field will just not exist.
4
+
5
+ - create! and save! will now properly raise an error
6
+ when a database operation fails. This is handled
7
+ by using :safe => true as an option to
8
+ collection.save.
9
+
10
+ - Setting blank numbers casts to nil.
11
+
12
+ - Criteria and named scopes can now be used on has
13
+ many relationships. They can be chained with each
14
+ other off the association chain.
15
+
16
+ - Mongoid can now determine if a document matches a
17
+ mongodb selector without hitting the database via
18
+ Document#matches?(selector).
19
+
20
+ - Overall performance improvements in all areas.
21
+
22
+ - Ruby 1.9 compatibility fixes.
23
+
24
+ - Has many related now supports finding by id or
25
+ by an optional :all, :first, :last with a
26
+ conditions hash.
27
+
28
+ === 1.0.6
29
+
30
+ - Preventing the setting of empty values in attributes.
31
+
32
+ - Better inspect formatting
33
+
34
+ === 1.0.5
35
+
36
+ - Has one and has many associations now set the parent
37
+ object first before writing the attributes on
38
+ #build and #create.
39
+
1
40
  === 1.0.4
2
41
  - Modified criteria to use the querying class
3
42
  when instantiating documents if there are no
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  gem.homepage = "http://mongoid.org"
13
13
  gem.authors = ["Durran Jordan"]
14
14
 
15
- gem.add_dependency("activesupport", ">= 2.2.2")
15
+ gem.add_dependency("activesupport", "<= 2.3.5")
16
16
  gem.add_dependency("mongo", ">= 0.18.2")
17
17
  gem.add_dependency("durran-validatable", ">= 2.0.1")
18
18
  gem.add_dependency("leshill-will_paginate", ">= 2.3.11")
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 1.1.0
@@ -21,7 +21,7 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  require "rubygems"
23
23
 
24
- gem "activesupport", ">= 2.2.2"
24
+ gem "activesupport", ">= 2.2.2", "<3.0.pre"
25
25
  gem "mongo", ">= 0.18.2"
26
26
  gem "durran-validatable", ">= 2.0.1"
27
27
  gem "leshill-will_paginate", ">= 2.3.11"
@@ -43,6 +43,7 @@ require "mongoid/attributes"
43
43
  require "mongoid/callbacks"
44
44
  require "mongoid/commands"
45
45
  require "mongoid/config"
46
+ require "mongoid/contexts"
46
47
  require "mongoid/complex_criterion"
47
48
  require "mongoid/criteria"
48
49
  require "mongoid/extensions"
@@ -52,6 +53,7 @@ require "mongoid/fields"
52
53
  require "mongoid/finders"
53
54
  require "mongoid/identity"
54
55
  require "mongoid/indexes"
56
+ require "mongoid/matchers"
55
57
  require "mongoid/memoization"
56
58
  require "mongoid/named_scope"
57
59
  require "mongoid/scope"
@@ -13,7 +13,7 @@ module Mongoid # :nodoc:
13
13
  base.class_eval do
14
14
  # Associations need to inherit down the chain.
15
15
  class_inheritable_accessor :associations
16
- self.associations = {}.with_indifferent_access
16
+ self.associations = {}
17
17
 
18
18
  include InstanceMethods
19
19
  extend ClassMethods
@@ -203,7 +203,7 @@ module Mongoid # :nodoc:
203
203
  #
204
204
  # <tt>Person.reflect_on_association(:addresses)</tt>
205
205
  def reflect_on_association(name)
206
- association = associations[name]
206
+ association = associations[name.to_s]
207
207
  association ? association.macro : nil
208
208
  end
209
209
 
@@ -212,7 +212,7 @@ module Mongoid # :nodoc:
212
212
  # then adds the accessors for the association. The defined setters and
213
213
  # getters for the associations will perform the necessary memoization.
214
214
  def add_association(type, options)
215
- name = options.name
215
+ name = options.name.to_s
216
216
  associations[name] = type
217
217
  define_method(name) do
218
218
  memoized(name) { type.instantiate(self, options) }
@@ -225,7 +225,7 @@ module Mongoid # :nodoc:
225
225
  # Adds a builder for a has_one association. This comes in the form of
226
226
  # build_name(attributes)
227
227
  def add_builder(type, options)
228
- name = options.name
228
+ name = options.name.to_s
229
229
  define_method("build_#{name}") do |attrs|
230
230
  reset(name) { type.new(self, attrs, options) }
231
231
  end
@@ -234,7 +234,7 @@ module Mongoid # :nodoc:
234
234
  # Adds a creator for a has_one association. This comes in the form of
235
235
  # create_name(attributes)
236
236
  def add_creator(type, options)
237
- name = options.name
237
+ name = options.name.to_s
238
238
  define_method("create_#{name}") do |attrs|
239
239
  document = send("build_#{name}", attrs)
240
240
  document.save; document
@@ -41,7 +41,6 @@ module Mongoid #:nodoc:
41
41
  object = type ? type.instantiate : @klass.instantiate
42
42
  object.parentize(@parent, @association_name)
43
43
  object.write_attributes(attrs)
44
- object.identify
45
44
  @documents << object
46
45
  object
47
46
  end
@@ -84,8 +83,15 @@ module Mongoid #:nodoc:
84
83
  end : []
85
84
  end
86
85
 
87
- # Delegate all missing methods over to the documents array.
86
+ # Delegate all missing methods over to the documents array unless a
87
+ # criteria or named scope exists on the association class. If that is the
88
+ # case then call that method.
88
89
  def method_missing(name, *args, &block)
90
+ unless @documents.respond_to?(name)
91
+ criteria = @klass.send(name, *args)
92
+ criteria.documents = @documents
93
+ return criteria
94
+ end
89
95
  @documents.send(name, *args, &block)
90
96
  end
91
97
 
@@ -1,7 +1,8 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid #:nodoc:
3
3
  module Associations #:nodoc:
4
- class HasManyRelated < DelegateClass(Array) #:nodoc:
4
+ class HasManyRelated #:nodoc:
5
+ include Proxy
5
6
 
6
7
  attr_reader :klass
7
8
 
@@ -46,8 +47,9 @@ module Mongoid #:nodoc:
46
47
 
47
48
  # Finds a document in this association.
48
49
  # If an id is passed, will return the document for that id.
49
- def find(id)
50
- @klass.find(id)
50
+ def find(*args)
51
+ args[1][:conditions].merge!(@foreign_key.to_sym => @parent.id) if args.size > 1
52
+ @klass.find(*args)
51
53
  end
52
54
 
53
55
  # Initializing a related association only requires looking up the objects
@@ -61,7 +63,11 @@ module Mongoid #:nodoc:
61
63
  @parent, @klass = document, options.klass
62
64
  @foreign_key = document.class.to_s.foreign_key
63
65
  @documents = @klass.all(:conditions => { @foreign_key => document.id })
64
- super(@documents)
66
+ end
67
+
68
+ # Delegate all missing methods over to the documents array.
69
+ def method_missing(name, *args, &block)
70
+ @documents.send(name, *args, &block)
65
71
  end
66
72
 
67
73
  # Delegates to <<
@@ -11,12 +11,12 @@ module Mongoid #:nodoc:
11
11
  # Get the id associated with this object. This will pull the _id value out
12
12
  # of the attributes +Hash+.
13
13
  def id
14
- @attributes[:_id]
14
+ @attributes["_id"]
15
15
  end
16
16
 
17
17
  # Set the id of the +Document+ to a new one.
18
18
  def id=(new_id)
19
- @attributes[:_id] = new_id
19
+ @attributes["_id"] = new_id
20
20
  end
21
21
 
22
22
  alias :_id :id
@@ -26,7 +26,12 @@ module Mongoid #:nodoc:
26
26
  def method_missing(name, *args)
27
27
  attr = name.to_s
28
28
  return super unless @attributes.has_key?(attr.reader)
29
- attr.writer? ? (@attributes[attr.reader] = *args) : @attributes[attr.reader]
29
+ if attr.writer?
30
+ # "args.size > 1" allows to simulate 1.8 behavior of "*args"
31
+ @attributes[attr.reader] = (args.size > 1) ? args : args.first
32
+ else
33
+ @attributes[attr.reader]
34
+ end
30
35
  end
31
36
 
32
37
  # Process the provided attributes casting them to their proper values if a
@@ -36,9 +41,9 @@ module Mongoid #:nodoc:
36
41
  def process(attrs = {})
37
42
  attrs.each_pair do |key, value|
38
43
  if Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
39
- @attributes[key] = value
44
+ @attributes[key.to_s] = value
40
45
  else
41
- send("#{key}=", value)
46
+ send("#{key}=", value) if value
42
47
  end
43
48
  end
44
49
  end
@@ -54,7 +59,8 @@ module Mongoid #:nodoc:
54
59
  #
55
60
  # <tt>person.read_attribute(:title)</tt>
56
61
  def read_attribute(name)
57
- fields[name].get(@attributes[name])
62
+ access = name.to_s
63
+ fields[access].get(@attributes[access])
58
64
  end
59
65
 
60
66
  # Remove a value from the +Document+ attributes. If the value does not exist
@@ -68,19 +74,19 @@ module Mongoid #:nodoc:
68
74
  #
69
75
  # <tt>person.remove_attribute(:title)</tt>
70
76
  def remove_attribute(name)
71
- @attributes.delete(name)
77
+ @attributes.delete(name.to_s)
72
78
  end
73
79
 
74
80
  # Returns the object type. This corresponds to the name of the class that
75
81
  # this +Document+ is, which is used in determining the class to
76
82
  # instantiate in various cases.
77
83
  def _type
78
- @attributes[:_type]
84
+ @attributes["_type"]
79
85
  end
80
86
 
81
87
  # Set the type of the +Document+. This should be the name of the class.
82
88
  def _type=(new_type)
83
- @attributes[:_type] = new_type
89
+ @attributes["_type"] = new_type
84
90
  end
85
91
 
86
92
  # Write a single attribute to the +Document+ attribute +Hash+. This will
@@ -99,10 +105,9 @@ module Mongoid #:nodoc:
99
105
  # This will also cause the observing +Document+ to notify it's parent if
100
106
  # there is any.
101
107
  def write_attribute(name, value)
102
- run_callbacks(:before_update)
103
- @attributes[name] = fields[name].set(value)
104
- run_callbacks(:after_update)
105
- notify
108
+ access = name.to_s
109
+ @attributes[access] = fields[access].set(value)
110
+ notify unless id.blank?
106
111
  end
107
112
 
108
113
  # Writes the supplied attributes +Hash+ to the +Document+. This will only
@@ -121,6 +126,7 @@ module Mongoid #:nodoc:
121
126
  # there is any.
122
127
  def write_attributes(attrs = nil)
123
128
  process(attrs || {})
129
+ identify if id.blank?
124
130
  notify
125
131
  end
126
132
 
@@ -59,10 +59,10 @@ module Mongoid #:nodoc:
59
59
  # <tt>document.save(false) # save without validations</tt>
60
60
  #
61
61
  # Returns: true if validation passes, false if not.
62
- def save(validate = true)
62
+ def save(validate = true, safe = false)
63
63
  new = new_record?
64
64
  run_callbacks(:before_create) if new
65
- saved = Save.execute(self, validate)
65
+ saved = Save.execute(self, validate, safe)
66
66
  run_callbacks(:after_create) if new
67
67
  saved
68
68
  end
@@ -76,7 +76,7 @@ module Mongoid #:nodoc:
76
76
  #
77
77
  # Returns: true if validation passes
78
78
  def save!
79
- return save(true) || (raise Errors::Validations.new(self.errors))
79
+ return save(true, true) || (raise Errors::Validations.new(self.errors))
80
80
  end
81
81
 
82
82
  # Update the document attributes and persist the document to the
@@ -86,7 +86,7 @@ module Mongoid #:nodoc:
86
86
  #
87
87
  # <tt>document.update_attributes(:title => "Test")</tt>
88
88
  def update_attributes(attrs = {})
89
- write_attributes(attrs); save
89
+ set_attributes(attrs); save
90
90
  end
91
91
 
92
92
  # Update the document attributes and persist the document to the
@@ -96,7 +96,13 @@ module Mongoid #:nodoc:
96
96
  #
97
97
  # <tt>document.update_attributes!(:title => "Test")</tt>
98
98
  def update_attributes!(attrs = {})
99
- write_attributes(attrs); save!
99
+ set_attributes(attrs); save!
100
+ end
101
+
102
+ protected
103
+ def set_attributes(attrs = {})
104
+ run_callbacks(:before_update)
105
+ write_attributes(attrs)
100
106
  end
101
107
 
102
108
  end
@@ -126,7 +132,7 @@ module Mongoid #:nodoc:
126
132
  #
127
133
  # Returns: the +Document+.
128
134
  def create!(attributes = {})
129
- document = Create.execute(new(attributes))
135
+ document = Create.execute(new(attributes), true, true)
130
136
  raise Errors::Validations.new(self.errors) unless document.errors.empty?
131
137
  return document
132
138
  end
@@ -10,9 +10,9 @@ module Mongoid #:nodoc:
10
10
  # doc: A new +Document+ that is going to be persisted.
11
11
  #
12
12
  # Returns: +Document+.
13
- def self.execute(doc, validate = true)
13
+ def self.execute(doc, validate = true, safe = false)
14
14
  doc.run_callbacks :before_create
15
- Save.execute(doc, validate)
15
+ Save.execute(doc, validate, safe)
16
16
  doc.run_callbacks :after_create
17
17
  return doc
18
18
  end
@@ -15,7 +15,7 @@ module Mongoid #:nodoc:
15
15
  # <tt>DeleteAll.execute(Person, :conditions => { :field => "value" })</tt>
16
16
  def self.execute(klass, params = {})
17
17
  collection = klass.collection
18
- params.any? ? collection.remove(params[:conditions].merge(:_type => klass.name)) : collection.drop
18
+ collection.remove((params[:conditions] || {}).merge(:_type => klass.name))
19
19
  end
20
20
  end
21
21
  end
@@ -10,12 +10,12 @@ module Mongoid #:nodoc:
10
10
  # doc: A +Document+ that is going to be persisted.
11
11
  #
12
12
  # Returns: +Document+ if validation passes, +false+ if not.
13
- def self.execute(doc, validate = true)
13
+ def self.execute(doc, validate = true, safe = false)
14
14
  return false if validate && !doc.valid?
15
15
  doc.run_callbacks :before_save
16
16
  parent = doc._parent
17
17
  doc.new_record = false
18
- if parent ? Save.execute(parent, validate) : doc.collection.save(doc.attributes)
18
+ if parent ? Save.execute(parent, validate, safe) : doc.collection.save(doc.attributes, :safe => safe)
19
19
  doc.run_callbacks :after_save
20
20
  return true
21
21
  else
@@ -11,6 +11,7 @@ module Mongoid #:nodoc
11
11
  include Commands
12
12
  include Fields
13
13
  include Indexes
14
+ include Matchers
14
15
  include Memoization
15
16
  include Observable
16
17
  include Validatable
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ require "mongoid/contexts/paging"
3
+ require "mongoid/contexts/enumerable"
4
+ require "mongoid/contexts/mongo"
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Enumerable
5
+ include Paging
6
+ attr_reader :selector, :options, :documents
7
+
8
+ delegate :first, :last, :to => :documents
9
+
10
+ # Return aggregation counts of the grouped documents. This will count by
11
+ # the first field provided in the fields array.
12
+ #
13
+ # Returns:
14
+ #
15
+ # A +Hash+ with field values as keys, count as values
16
+ def aggregate
17
+ counts = {}
18
+ group.each_pair { |key, value| counts[key] = value.size }
19
+ counts
20
+ end
21
+
22
+ # Gets the number of documents in the array. Delegates to size.
23
+ def count
24
+ @documents.size
25
+ end
26
+
27
+ # Groups the documents by the first field supplied in the field options.
28
+ #
29
+ # Returns:
30
+ #
31
+ # A +Hash+ with field values as keys, arrays of documents as values.
32
+ def group
33
+ field = @options[:fields].first
34
+ @documents.group_by { |doc| doc.send(field) }
35
+ end
36
+
37
+ # Enumerable implementation of execute. Returns matching documents for
38
+ # the selector, and adds options if supplied.
39
+ #
40
+ # Returns:
41
+ #
42
+ # An +Array+ of documents that matched the selector.
43
+ def execute
44
+ @documents.select { |document| document.matches?(@selector) }
45
+ end
46
+
47
+ # Create the new enumerable context. This will need the selector and
48
+ # options from a +Criteria+ and a documents array that is the underlying
49
+ # array of embedded documents from a has many association.
50
+ #
51
+ # Example:
52
+ #
53
+ # <tt>Mongoid::Contexts::Enumerable.new(selector, options, docs)</tt>
54
+ def initialize(selector, options, documents)
55
+ @selector, @options, @documents = selector, options, documents
56
+ end
57
+
58
+ # Get the largest value for the field in all the documents.
59
+ #
60
+ # Returns:
61
+ #
62
+ # The numerical largest value.
63
+ def max(field)
64
+ largest = @documents.inject(nil) do |memo, doc|
65
+ value = doc.send(field)
66
+ (memo && memo >= value) ? memo : value
67
+ end
68
+ end
69
+
70
+ # Get the smallest value for the field in all the documents.
71
+ #
72
+ # Returns:
73
+ #
74
+ # The numerical smallest value.
75
+ def min(field)
76
+ smallest = @documents.inject(nil) do |memo, doc|
77
+ value = doc.send(field)
78
+ (memo && memo <= value) ? memo : value
79
+ end
80
+ end
81
+
82
+ # Get one document.
83
+ #
84
+ # Returns:
85
+ #
86
+ # The first document in the +Array+
87
+ def one
88
+ @documents.first
89
+ end
90
+
91
+ # Get the sum of the field values for all the documents.
92
+ #
93
+ # Returns:
94
+ #
95
+ # The numerical sum of all the document field values.
96
+ def sum(field)
97
+ sum = @documents.inject(nil) do |memo, doc|
98
+ value = doc.send(field)
99
+ memo ? memo += value : value
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+ end