mongoid 1.1.4 → 1.2.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 (65) hide show
  1. data/HISTORY +69 -1
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongoid.rb +39 -13
  5. data/lib/mongoid/associations.rb +1 -0
  6. data/lib/mongoid/associations/has_many.rb +19 -3
  7. data/lib/mongoid/attributes.rb +6 -1
  8. data/lib/mongoid/collection.rb +106 -0
  9. data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
  10. data/lib/mongoid/collections/master.rb +28 -0
  11. data/lib/mongoid/collections/mimic.rb +46 -0
  12. data/lib/mongoid/collections/operations.rb +39 -0
  13. data/lib/mongoid/collections/slaves.rb +44 -0
  14. data/lib/mongoid/commands.rb +1 -0
  15. data/lib/mongoid/config.rb +61 -9
  16. data/lib/mongoid/contexts/enumerable.rb +24 -15
  17. data/lib/mongoid/contexts/mongo.rb +25 -31
  18. data/lib/mongoid/contexts/paging.rb +2 -2
  19. data/lib/mongoid/criteria.rb +48 -7
  20. data/lib/mongoid/criterion/exclusion.rb +2 -0
  21. data/lib/mongoid/criterion/optional.rb +2 -2
  22. data/lib/mongoid/cursor.rb +82 -0
  23. data/lib/mongoid/document.rb +12 -13
  24. data/lib/mongoid/errors.rb +35 -4
  25. data/lib/mongoid/extensions.rb +1 -0
  26. data/lib/mongoid/extensions/array/aliasing.rb +4 -0
  27. data/lib/mongoid/extensions/string/inflections.rb +44 -1
  28. data/lib/mongoid/factory.rb +17 -0
  29. data/lib/mongoid/finders.rb +7 -2
  30. data/lib/mongoid/matchers/default.rb +6 -0
  31. data/lib/mongoid/matchers/gt.rb +1 -1
  32. data/lib/mongoid/matchers/gte.rb +1 -1
  33. data/lib/mongoid/matchers/lt.rb +1 -1
  34. data/lib/mongoid/matchers/lte.rb +1 -1
  35. data/mongoid.gemspec +30 -5
  36. data/perf/benchmark.rb +5 -3
  37. data/spec/integration/mongoid/associations_spec.rb +12 -0
  38. data/spec/integration/mongoid/contexts/enumerable_spec.rb +20 -0
  39. data/spec/integration/mongoid/criteria_spec.rb +28 -0
  40. data/spec/integration/mongoid/document_spec.rb +1 -1
  41. data/spec/integration/mongoid/inheritance_spec.rb +2 -2
  42. data/spec/models/person.rb +1 -1
  43. data/spec/spec_helper.rb +9 -4
  44. data/spec/unit/mongoid/associations/has_many_spec.rb +19 -0
  45. data/spec/unit/mongoid/associations_spec.rb +9 -0
  46. data/spec/unit/mongoid/attributes_spec.rb +4 -4
  47. data/spec/unit/mongoid/collection_spec.rb +113 -0
  48. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
  49. data/spec/unit/mongoid/collections/master_spec.rb +41 -0
  50. data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
  51. data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
  52. data/spec/unit/mongoid/commands_spec.rb +7 -0
  53. data/spec/unit/mongoid/config_spec.rb +52 -1
  54. data/spec/unit/mongoid/contexts/enumerable_spec.rb +38 -12
  55. data/spec/unit/mongoid/contexts/mongo_spec.rb +38 -31
  56. data/spec/unit/mongoid/criteria_spec.rb +20 -12
  57. data/spec/unit/mongoid/criterion/exclusion_spec.rb +26 -0
  58. data/spec/unit/mongoid/criterion/optional_spec.rb +13 -0
  59. data/spec/unit/mongoid/cursor_spec.rb +74 -0
  60. data/spec/unit/mongoid/document_spec.rb +4 -5
  61. data/spec/unit/mongoid/errors_spec.rb +5 -9
  62. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +21 -2
  63. data/spec/unit/mongoid/factory_spec.rb +16 -0
  64. data/spec/unit/mongoid_spec.rb +4 -4
  65. metadata +28 -3
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Collections #:nodoc:
4
+ module Operations #:nodoc:
5
+ # Constant definining all the read operations available for a
6
+ # Mongo:Collection. This is used in delegation.
7
+ READ = [
8
+ :[],
9
+ :count,
10
+ :distinct,
11
+ :find,
12
+ :find_one,
13
+ :group,
14
+ :index_information,
15
+ :map_reduce,
16
+ :mapreduce,
17
+ :options
18
+ ]
19
+
20
+ # Constant definining all the write operations available for a
21
+ # Mongo:Collection. This is used in delegation.
22
+ WRITE = [
23
+ :<<,
24
+ :create_index,
25
+ :drop,
26
+ :drop_index,
27
+ :drop_indexes,
28
+ :insert,
29
+ :remove,
30
+ :rename,
31
+ :save,
32
+ :update
33
+ ]
34
+
35
+ # Convenience constant for getting back all collection operations.
36
+ ALL = (READ + WRITE)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Collections #:nodoc:
4
+ class Slaves
5
+ include Mimic
6
+
7
+ attr_reader :iterator
8
+
9
+ # All read operations should delegate to the slave connections.
10
+ # These operations mimic the methods on a Mongo:Collection.
11
+ #
12
+ # Example:
13
+ #
14
+ # <tt>collection.save({ :name => "Al" })</tt>
15
+ proxy(:collection, Operations::READ)
16
+
17
+ # Is the collection of slaves empty or not?
18
+ #
19
+ # Return:
20
+ #
21
+ # True is the iterator is not set, false if not.
22
+ def empty?
23
+ @iterator.nil?
24
+ end
25
+
26
+ # Create the new database reader. Will create a collection from the
27
+ # slave databases and cycle through them on each read.
28
+ #
29
+ # Example:
30
+ #
31
+ # <tt>Reader.new(slaves, "mongoid_people")</tt>
32
+ def initialize(slaves, name)
33
+ unless slaves.blank?
34
+ @iterator = CyclicIterator.new(slaves.collect { |db| db.collection(name) })
35
+ end
36
+ end
37
+
38
+ protected
39
+ def collection
40
+ @iterator.next
41
+ end
42
+ end
43
+ end
44
+ end
@@ -107,6 +107,7 @@ module Mongoid #:nodoc:
107
107
  def set_attributes(attrs = {})
108
108
  run_callbacks(:before_update)
109
109
  write_attributes(attrs)
110
+ run_callbacks(:after_update)
110
111
  end
111
112
 
112
113
  end
@@ -5,28 +5,80 @@ module Mongoid #:nodoc
5
5
 
6
6
  attr_accessor \
7
7
  :allow_dynamic_fields,
8
+ :max_successive_reads,
9
+ :reconnect_time,
10
+ :parameterize_keys,
8
11
  :persist_in_safe_mode,
9
12
  :raise_not_found_error
10
13
 
11
14
  # Defaults the configuration options to true.
12
15
  def initialize
13
16
  @allow_dynamic_fields = true
17
+ @max_successive_reads = 10
18
+ @parameterize_keys = true
14
19
  @persist_in_safe_mode = true
15
20
  @raise_not_found_error = true
21
+ @reconnect_time = 3
16
22
  end
17
23
 
18
- # Sets the Mongo::DB to be used.
19
- def database=(db)
20
- raise Errors::InvalidDatabase.new(
21
- "Database should be a Mongo::DB, not #{db.class.name}"
22
- ) unless db.kind_of?(Mongo::DB)
23
- @database = db
24
+ # Sets the Mongo::DB master database to be used. If the object trying to me
25
+ # set is not a valid +Mongo::DB+, then an error will be raise.
26
+ #
27
+ # Example:
28
+ #
29
+ # <tt>Config.master = Mongo::Connection.db("test")</tt>
30
+ #
31
+ # Returns:
32
+ #
33
+ # The Master DB instance.
34
+ def master=(db)
35
+ raise Errors::InvalidDatabase.new(db) unless db.kind_of?(Mongo::DB)
36
+ @master = db
24
37
  end
25
38
 
26
- # Returns the Mongo::DB to use or raise an error if none was set.
27
- def database
28
- @database || (raise Errors::InvalidDatabase.new("No database has been set, please use Mongoid.database="))
39
+ # Returns the master database, or if none has been set it will raise an
40
+ # error.
41
+ #
42
+ # Example:
43
+ #
44
+ # <tt>Config.master</tt>
45
+ #
46
+ # Returns:
47
+ #
48
+ # The master +Mongo::DB+
49
+ def master
50
+ @master || (raise Errors::InvalidDatabase.new(nil))
29
51
  end
30
52
 
53
+ alias :database :master
54
+ alias :database= :master=
55
+
56
+ # Sets the Mongo::DB slave databases to be used. If the objects trying to me
57
+ # set are not valid +Mongo::DBs+, then an error will be raise.
58
+ #
59
+ # Example:
60
+ #
61
+ # <tt>Config.slaves = [ Mongo::Connection.db("test") ]</tt>
62
+ #
63
+ # Returns:
64
+ #
65
+ # The slaves DB instances.
66
+ def slaves=(dbs)
67
+ dbs.each { |db| raise Errors::InvalidDatabase.new(db) unless db.kind_of?(Mongo::DB) }
68
+ @slaves = dbs
69
+ end
70
+
71
+ # Returns the slave databases, or if none has been set nil
72
+ #
73
+ # Example:
74
+ #
75
+ # <tt>Config.slaves</tt>
76
+ #
77
+ # Returns:
78
+ #
79
+ # The slave +Mongo::DBs+
80
+ def slaves
81
+ @slaves
82
+ end
31
83
  end
32
84
  end
@@ -5,7 +5,7 @@ module Mongoid #:nodoc:
5
5
  include Paging
6
6
  attr_reader :selector, :options, :documents
7
7
 
8
- delegate :first, :last, :to => :documents
8
+ delegate :first, :last, :to => :execute
9
9
 
10
10
  # Return aggregation counts of the grouped documents. This will count by
11
11
  # the first field provided in the fields array.
@@ -21,7 +21,7 @@ module Mongoid #:nodoc:
21
21
 
22
22
  # Gets the number of documents in the array. Delegates to size.
23
23
  def count
24
- @documents.size
24
+ @count ||= @documents.size
25
25
  end
26
26
 
27
27
  # Groups the documents by the first field supplied in the field options.
@@ -40,8 +40,8 @@ module Mongoid #:nodoc:
40
40
  # Returns:
41
41
  #
42
42
  # An +Array+ of documents that matched the selector.
43
- def execute
44
- @documents.select { |document| document.matches?(@selector) }
43
+ def execute(paginating = false)
44
+ limit(@documents.select { |document| document.matches?(@selector) })
45
45
  end
46
46
 
47
47
  # Create the new enumerable context. This will need the selector and
@@ -61,10 +61,7 @@ module Mongoid #:nodoc:
61
61
  #
62
62
  # The numerical largest value.
63
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
64
+ determine(field, :>=)
68
65
  end
69
66
 
70
67
  # Get the smallest value for the field in all the documents.
@@ -73,10 +70,7 @@ module Mongoid #:nodoc:
73
70
  #
74
71
  # The numerical smallest value.
75
72
  def min(field)
76
- smallest = @documents.inject(nil) do |memo, doc|
77
- value = doc.send(field)
78
- (memo && memo <= value) ? memo : value
79
- end
73
+ determine(field, :<=)
80
74
  end
81
75
 
82
76
  # Get one document.
@@ -84,9 +78,7 @@ module Mongoid #:nodoc:
84
78
  # Returns:
85
79
  #
86
80
  # The first document in the +Array+
87
- def one
88
- @documents.first
89
- end
81
+ alias :one :first
90
82
 
91
83
  # Get the sum of the field values for all the documents.
92
84
  #
@@ -100,6 +92,23 @@ module Mongoid #:nodoc:
100
92
  end
101
93
  end
102
94
 
95
+ protected
96
+ # If the field exists, perform the comparison and set if true.
97
+ def determine(field, operator)
98
+ matching = @documents.inject(nil) do |memo, doc|
99
+ value = doc.send(field)
100
+ (memo && memo.send(operator, value)) ? memo : value
101
+ end
102
+ end
103
+
104
+ # Limits the result set if skip and limit options.
105
+ def limit(documents)
106
+ skip, limit = @options[:skip], @options[:limit]
107
+ if skip && limit
108
+ return documents.slice(skip, limit)
109
+ end
110
+ documents
111
+ end
103
112
  end
104
113
  end
105
114
  end
@@ -35,6 +35,28 @@ module Mongoid #:nodoc:
35
35
  @count ||= @klass.collection.find(@selector, process_options).count
36
36
  end
37
37
 
38
+ # Execute the context. This will take the selector and options
39
+ # and pass them on to the Ruby driver's +find()+ method on the collection. The
40
+ # collection itself will be retrieved from the class provided, and once the
41
+ # query has returned new documents of the type of class provided will be instantiated.
42
+ #
43
+ # Example:
44
+ #
45
+ # <tt>mongo.execute</tt>
46
+ #
47
+ # Returns:
48
+ #
49
+ # An enumerable +Cursor+.
50
+ def execute(paginating = false)
51
+ cursor = @klass.collection.find(@selector, process_options)
52
+ if cursor
53
+ @count = cursor.count if paginating
54
+ cursor
55
+ else
56
+ []
57
+ end
58
+ end
59
+
38
60
  GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
39
61
  # Groups the context. This will take the internally built selector and options
40
62
  # and pass them on to the Ruby driver's +group()+ method on the collection. The
@@ -57,34 +79,12 @@ module Mongoid #:nodoc:
57
79
  true
58
80
  ).collect do |docs|
59
81
  docs["group"] = docs["group"].collect do |attrs|
60
- instantiate(attrs)
82
+ Mongoid::Factory.build(attrs)
61
83
  end
62
84
  docs
63
85
  end
64
86
  end
65
87
 
66
- # Execute the context. This will take the selector and options
67
- # and pass them on to the Ruby driver's +find()+ method on the collection. The
68
- # collection itself will be retrieved from the class provided, and once the
69
- # query has returned new documents of the type of class provided will be instantiated.
70
- #
71
- # Example:
72
- #
73
- # <tt>mongo.execute</tt>
74
- #
75
- # Returns:
76
- #
77
- # An +Array+ of documents
78
- def execute
79
- attributes = @klass.collection.find(@selector, process_options)
80
- if attributes
81
- @count = attributes.count
82
- attributes.collect { |doc| instantiate(doc) }
83
- else
84
- []
85
- end
86
- end
87
-
88
88
  # Create the new mongo context. This will execute the queries given the
89
89
  # selector and options against the database.
90
90
  #
@@ -115,7 +115,7 @@ module Mongoid #:nodoc:
115
115
  sorting = [[:_id, :asc]] unless sorting
116
116
  opts[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
117
117
  attributes = @klass.collection.find_one(@selector, opts)
118
- attributes ? instantiate(attributes) : nil
118
+ attributes ? Mongoid::Factory.build(attributes) : nil
119
119
  end
120
120
 
121
121
  MAX_REDUCE = "function(obj, prev) { if (prev.max == 'start') { prev.max = obj.[field]; } " +
@@ -169,7 +169,7 @@ module Mongoid #:nodoc:
169
169
  # The first document in the collection.
170
170
  def one
171
171
  attributes = @klass.collection.find_one(@selector, process_options)
172
- attributes ? instantiate(attributes) : nil
172
+ attributes ? Mongoid::Factory.build(attributes) : nil
173
173
  end
174
174
 
175
175
  alias :first :one
@@ -193,7 +193,6 @@ module Mongoid #:nodoc:
193
193
  grouped(:sum, field.to_s, SUM_REDUCE)
194
194
  end
195
195
 
196
- protected
197
196
  # Common functionality for grouping operations. Currently used by min, max
198
197
  # and sum. Will gsub the field name in the supplied reduce function.
199
198
  def grouped(start, field, reduce)
@@ -207,11 +206,6 @@ module Mongoid #:nodoc:
207
206
  collection.first[start.to_s]
208
207
  end
209
208
 
210
- # If hereditary instantiate by _type otherwise use the klass.
211
- def instantiate(attrs)
212
- @hereditary ? attrs["_type"].constantize.instantiate(attrs) : @klass.instantiate(attrs)
213
- end
214
-
215
209
  # Filters the field list. If no fields have been supplied, then it will be
216
210
  # empty. If fields have been defined then _type will be included as well.
217
211
  def process_options
@@ -12,9 +12,9 @@ module Mongoid #:nodoc:
12
12
  #
13
13
  # A collection of documents paginated.
14
14
  def paginate
15
- @collection ||= execute
15
+ @collection ||= execute(true)
16
16
  WillPaginate::Collection.create(page, per_page, count) do |pager|
17
- pager.replace(@collection)
17
+ pager.replace(@collection.to_a)
18
18
  end
19
19
  end
20
20
 
@@ -25,8 +25,9 @@ module Mongoid #:nodoc:
25
25
  include Criterion::Optional
26
26
  include Enumerable
27
27
 
28
+ attr_reader :collection, :ids, :klass, :options, :selector
29
+
28
30
  attr_accessor :documents
29
- attr_reader :klass, :options, :selector
30
31
 
31
32
  delegate \
32
33
  :aggregate,
@@ -46,13 +47,13 @@ module Mongoid #:nodoc:
46
47
  # Concatinate the criteria with another enumerable. If the other is a
47
48
  # +Criteria+ then it needs to get the collection from it.
48
49
  def +(other)
49
- entries + (other.is_a?(Criteria) ? other.entries : other)
50
+ entries + comparable(other)
50
51
  end
51
52
 
52
53
  # Returns the difference between the criteria and another enumerable. If
53
54
  # the other is a +Criteria+ then it needs to get the collection from it.
54
55
  def -(other)
55
- entries - (other.is_a?(Criteria) ? other.entries : other)
56
+ entries - comparable(other)
56
57
  end
57
58
 
58
59
  # Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
@@ -69,7 +70,7 @@ module Mongoid #:nodoc:
69
70
  self.selector == other.selector && self.options == other.options
70
71
  when Enumerable
71
72
  @collection ||= execute
72
- return (@collection == other)
73
+ return (@collection.collect == other)
73
74
  else
74
75
  return false
75
76
  end
@@ -108,7 +109,11 @@ module Mongoid #:nodoc:
108
109
  # <tt>criteria.each { |doc| p doc }</tt>
109
110
  def each(&block)
110
111
  @collection ||= execute
111
- block_given? ? @collection.each { |doc| yield doc } : self
112
+ if block_given?
113
+ docs = []
114
+ @collection.each { |doc| docs << doc; yield doc }
115
+ end
116
+ self
112
117
  end
113
118
 
114
119
  # Create the new +Criteria+ object. This will initialize the selector
@@ -219,7 +224,18 @@ module Mongoid #:nodoc:
219
224
  end
220
225
 
221
226
  protected
222
- # Determines the context to be used for this criteria.
227
+ # Determines the context to be used for this criteria. If the class is an
228
+ # embedded document, then thw context will be the array in the has_many
229
+ # association it is in. If the class is a root, then the database itself
230
+ # will be the context.
231
+ #
232
+ # Example:
233
+ #
234
+ # <tt>criteria#determine_context</tt>
235
+ #
236
+ # Returns:
237
+ #
238
+ # A enumerable or mongo context.
223
239
  def determine_context
224
240
  if @klass.embedded
225
241
  return Contexts::Enumerable.new(@selector, @options, @documents)
@@ -230,6 +246,12 @@ module Mongoid #:nodoc:
230
246
  # Filters the unused options out of the options +Hash+. Currently this
231
247
  # takes into account the "page" and "per_page" options that would be passed
232
248
  # in if using will_paginate.
249
+ #
250
+ # Example:
251
+ #
252
+ # Given a criteria with a selector of { :page => 1, :per_page => 40 }
253
+ #
254
+ # <tt>criteria.filter_options</tt> # selector: { :skip => 0, :limit => 40 }
233
255
  def filter_options
234
256
  page_num = @options.delete(:page)
235
257
  per_page_num = @options.delete(:per_page)
@@ -239,14 +261,33 @@ module Mongoid #:nodoc:
239
261
  end
240
262
  end
241
263
 
264
+ # Return the entries of the other criteria or the object. Used for
265
+ # comparing criteria or an enumerable.
266
+ def comparable(other)
267
+ other.is_a?(Criteria) ? other.entries : other
268
+ end
269
+
242
270
  # Update the selector setting the operator on the value for each key in the
243
271
  # supplied attributes +Hash+.
272
+ #
273
+ # Example:
274
+ #
275
+ # <tt>criteria.update_selector({ :field => "value" }, "$in")</tt>
244
276
  def update_selector(attributes, operator)
245
277
  attributes.each { |key, value| @selector[key] = { operator => value } }; self
246
278
  end
247
279
 
248
280
  class << self
249
- # Return a criteria or single document based on an id search.
281
+ # Create a criteria or single document based on an id search. Will handle
282
+ # if a single id has been passed or mulitple ids.
283
+ #
284
+ # Example:
285
+ #
286
+ # Criteria.id_criteria(Person, [1, 2, 3])
287
+ #
288
+ # Returns:
289
+ #
290
+ # The single or multiple documents.
250
291
  def id_criteria(klass, params)
251
292
  criteria = new(klass).id(params)
252
293
  result = params.is_a?(String) ? criteria.one : criteria.entries