mongoid 1.1.4 → 1.2.0

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