mongoid 1.0.6 → 1.1.0

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