mongoid 3.0.23 → 3.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 (95) hide show
  1. data/CHANGELOG.md +253 -9
  2. data/LICENSE +1 -1
  3. data/README.md +4 -1
  4. data/lib/config/locales/en.yml +7 -6
  5. data/lib/mongoid.rb +18 -1
  6. data/lib/mongoid/atomic.rb +22 -20
  7. data/lib/mongoid/atomic/paths/embedded.rb +19 -5
  8. data/lib/mongoid/atomic/paths/root.rb +1 -1
  9. data/lib/mongoid/atomic/positionable.rb +73 -0
  10. data/lib/mongoid/attributes.rb +63 -1
  11. data/lib/mongoid/callbacks.rb +58 -4
  12. data/lib/mongoid/components.rb +8 -3
  13. data/lib/mongoid/config.rb +71 -23
  14. data/lib/mongoid/contextual.rb +2 -1
  15. data/lib/mongoid/contextual/aggregable/mongo.rb +27 -63
  16. data/lib/mongoid/contextual/atomic.rb +4 -3
  17. data/lib/mongoid/contextual/find_and_modify.rb +1 -1
  18. data/lib/mongoid/contextual/geo_near.rb +238 -0
  19. data/lib/mongoid/contextual/map_reduce.rb +12 -1
  20. data/lib/mongoid/contextual/memory.rb +36 -31
  21. data/lib/mongoid/contextual/mongo.rb +147 -91
  22. data/lib/mongoid/contextual/queryable.rb +25 -0
  23. data/lib/mongoid/copyable.rb +4 -1
  24. data/lib/mongoid/criteria.rb +23 -275
  25. data/lib/mongoid/criterion/findable.rb +179 -0
  26. data/lib/mongoid/criterion/modifiable.rb +191 -0
  27. data/lib/mongoid/criterion/scoping.rb +11 -6
  28. data/lib/mongoid/document.rb +7 -56
  29. data/lib/mongoid/equality.rb +66 -0
  30. data/lib/mongoid/errors/mongoid_error.rb +7 -3
  31. data/lib/mongoid/extensions/array.rb +13 -1
  32. data/lib/mongoid/extensions/date.rb +9 -2
  33. data/lib/mongoid/extensions/hash.rb +38 -2
  34. data/lib/mongoid/extensions/nil_class.rb +12 -0
  35. data/lib/mongoid/extensions/object.rb +24 -0
  36. data/lib/mongoid/extensions/string.rb +14 -2
  37. data/lib/mongoid/extensions/time.rb +4 -1
  38. data/lib/mongoid/fields.rb +49 -5
  39. data/lib/mongoid/fields/foreign_key.rb +12 -0
  40. data/lib/mongoid/fields/standard.rb +12 -0
  41. data/lib/mongoid/finders.rb +8 -0
  42. data/lib/mongoid/hierarchy.rb +19 -1
  43. data/lib/mongoid/indexes.rb +30 -4
  44. data/lib/mongoid/indexes/validators/options.rb +12 -2
  45. data/lib/mongoid/inspection.rb +2 -1
  46. data/lib/mongoid/matchers/strategies.rb +5 -5
  47. data/lib/mongoid/observer.rb +27 -36
  48. data/lib/mongoid/persistence.rb +42 -17
  49. data/lib/mongoid/persistence/atomic.rb +10 -5
  50. data/lib/mongoid/persistence/atomic/operation.rb +26 -9
  51. data/lib/mongoid/persistence/atomic/unset.rb +1 -1
  52. data/lib/mongoid/persistence/operations/embedded/insert.rb +5 -2
  53. data/lib/mongoid/persistence/operations/embedded/remove.rb +5 -2
  54. data/lib/mongoid/persistence/operations/update.rb +7 -3
  55. data/lib/mongoid/railties/database.rake +12 -19
  56. data/lib/mongoid/relations.rb +2 -0
  57. data/lib/mongoid/relations/accessors.rb +30 -8
  58. data/lib/mongoid/relations/binding.rb +5 -1
  59. data/lib/mongoid/relations/bindings/referenced/in.rb +1 -1
  60. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +3 -3
  61. data/lib/mongoid/relations/counter_cache.rb +107 -0
  62. data/lib/mongoid/relations/embedded/batchable.rb +13 -4
  63. data/lib/mongoid/relations/embedded/many.rb +30 -1
  64. data/lib/mongoid/relations/macros.rb +2 -0
  65. data/lib/mongoid/relations/marshalable.rb +0 -1
  66. data/lib/mongoid/relations/metadata.rb +63 -11
  67. data/lib/mongoid/relations/options.rb +1 -0
  68. data/lib/mongoid/relations/proxy.rb +45 -2
  69. data/lib/mongoid/relations/referenced/in.rb +11 -2
  70. data/lib/mongoid/relations/referenced/many.rb +31 -3
  71. data/lib/mongoid/relations/referenced/many_to_many.rb +31 -3
  72. data/lib/mongoid/relations/referenced/one.rb +1 -1
  73. data/lib/mongoid/relations/targets/enumerable.rb +5 -1
  74. data/lib/mongoid/relations/touchable.rb +35 -6
  75. data/lib/mongoid/reloading.rb +5 -3
  76. data/lib/mongoid/scoping.rb +2 -2
  77. data/lib/mongoid/sessions.rb +57 -7
  78. data/lib/mongoid/sessions/factory.rb +22 -1
  79. data/lib/mongoid/threaded.rb +4 -30
  80. data/lib/mongoid/threaded/lifecycle.rb +12 -12
  81. data/lib/mongoid/timestamps.rb +1 -0
  82. data/lib/mongoid/timestamps/created.rb +2 -0
  83. data/lib/mongoid/timestamps/created/short.rb +19 -0
  84. data/lib/mongoid/timestamps/short.rb +10 -0
  85. data/lib/mongoid/timestamps/updated.rb +2 -0
  86. data/lib/mongoid/timestamps/updated/short.rb +19 -0
  87. data/lib/mongoid/validations.rb +2 -0
  88. data/lib/mongoid/validations/queryable.rb +2 -2
  89. data/lib/mongoid/validations/uniqueness.rb +1 -18
  90. data/lib/mongoid/version.rb +1 -1
  91. data/lib/rails/generators/mongoid/model/model_generator.rb +1 -0
  92. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +3 -0
  93. data/lib/rails/mongoid.rb +53 -29
  94. data/lib/support/ruby_version.rb +26 -0
  95. metadata +18 -7
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
- require "mongoid/contextual/memory"
2
+ require "mongoid/contextual/queryable"
3
3
  require "mongoid/contextual/mongo"
4
+ require "mongoid/contextual/memory"
4
5
 
5
6
  module Mongoid
6
7
  module Contextual
@@ -24,8 +24,12 @@ module Mongoid
24
24
  # @since 3.0.0
25
25
  def aggregates(field)
26
26
  if query.count > 0
27
- map_reduce(mapper(field), reducer).
28
- out(inline: 1).finalize(finalizer).first["value"]
27
+ result = collection.aggregate(pipeline(field)).to_a
28
+ if result.empty?
29
+ { "count" => query.count, "avg" => 0, "sum" => 0 }
30
+ else
31
+ result.first
32
+ end
29
33
  else
30
34
  { "count" => 0 }
31
35
  end
@@ -109,73 +113,33 @@ module Mongoid
109
113
 
110
114
  private
111
115
 
112
- # Get the finalize function.
116
+ # Get the aggregation pipeline for provided field.
113
117
  #
114
118
  # @api private
115
119
  #
116
- # @example Get the finalize function.
117
- # aggregable.finalizer
118
- #
119
- # @return [ String ] The finalize JS function.
120
- #
121
- # @since 3.0.0
122
- def finalizer
123
- %Q{
124
- function(key, agg) {
125
- agg.avg = agg.sum / agg.count;
126
- return agg;
127
- }}
128
- end
129
-
130
- # Get the map function for the provided field.
131
- #
132
- # @api private
133
- #
134
- # @example Get the map function.
135
- # aggregable.mapper(:likes)
120
+ # @example Get the pipeline.
121
+ # aggregable.pipeline(:likes)
136
122
  #
137
123
  # @param [ String, Symbol ] field The name of the field.
138
124
  #
139
- # @return [ String ] The map JS function.
140
- #
141
- # @since 3.0.0
142
- def mapper(field)
143
- %Q{
144
- function() {
145
- var agg = {
146
- count: 1,
147
- max: this.#{field},
148
- min: this.#{field},
149
- sum: this.#{field}
150
- };
151
- emit("#{field}", agg);
152
- }}
153
- end
154
-
155
- # Get the reduce function for the provided field.
156
- #
157
- # @api private
158
- #
159
- # @example Get the reduce function.
160
- # aggregable.reducer(:likes)
161
- #
162
- # @return [ String ] The reduce JS function.
163
- #
164
- # @since 3.0.0
165
- def reducer
166
- %Q{
167
- function(key, values) {
168
- var agg = { count: 0, max: null, min: null, sum: 0 };
169
- values.forEach(function(val) {
170
- if (val.max !== null) {
171
- if (agg.max == null || val.max > agg.max) agg.max = val.max;
172
- if (agg.min == null || val.max < agg.min) agg.min = val.max;
173
- agg.sum += val.sum;
174
- }
175
- agg.count += val.count;
176
- });
177
- return agg;
178
- }}
125
+ # @return [ Array ] The array of pipeline operators.
126
+ #
127
+ # @since 3.1.0
128
+ def pipeline(field)
129
+ db_field = "$#{database_field_name(field)}"
130
+ pipeline = []
131
+ pipeline << { "$match" => criteria.nin(field => nil).selector }
132
+ pipeline << { "$limit" => criteria.options[:limit] } if criteria.options[:limit]
133
+ pipeline << {
134
+ "$group" => {
135
+ "_id" => field.to_s,
136
+ "count" => { "$sum" => 1 },
137
+ "max" => { "$max" => db_field },
138
+ "min" => { "$min" => db_field },
139
+ "sum" => { "$sum" => db_field },
140
+ "avg" => { "$avg" => db_field }
141
+ }
142
+ }
179
143
  end
180
144
  end
181
145
  end
@@ -166,13 +166,14 @@ module Mongoid
166
166
  # @example Unset the field on the matches.
167
167
  # context.unset(:name)
168
168
  #
169
- # @param [ String, Symbol ] field The name of the field.
169
+ # @param [ String, Symbol, Array ] fields The name of the fields.
170
170
  #
171
171
  # @return [ nil ] Nil.
172
172
  #
173
173
  # @since 3.0.0
174
- def unset(field)
175
- query.update_all("$unset" => { database_field_name(field) => true })
174
+ def unset(*args)
175
+ fields = args.__find_args__.collect { |f| [database_field_name(f), true] }
176
+ query.update_all("$unset" => Hash[fields])
176
177
  end
177
178
  end
178
179
  end
@@ -51,7 +51,7 @@ module Mongoid
51
51
  # @api private
52
52
  #
53
53
  # @example Apply the criteria options
54
- # map_reduce.apply_criteria_options
54
+ # find_and_modify.apply_criteria_options
55
55
  #
56
56
  # @return [ nil ] Nothing.
57
57
  #
@@ -0,0 +1,238 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Contextual
4
+ class GeoNear
5
+ include Enumerable
6
+ include Command
7
+
8
+ delegate :[], to: :results
9
+ delegate :==, :empty?, to: :entries
10
+
11
+ # Get the average distance for all documents from the point in the
12
+ # command.
13
+ #
14
+ # @example Get the average distance.
15
+ # geo_near.average_distance
16
+ #
17
+ # @return [ Float, nil ] The average distance.
18
+ #
19
+ # @since 3.1.0
20
+ def average_distance
21
+ average = stats["avgDistance"]
22
+ average.nan? ? nil : average
23
+ end
24
+
25
+ # Iterates over each of the documents in the $geoNear, excluding the
26
+ # extra information that was passed back from the database.
27
+ #
28
+ # @example Iterate over the results.
29
+ # geo_near.each do |doc|
30
+ # p doc
31
+ # end
32
+ #
33
+ # @return [ Enumerator ] The enumerator.
34
+ #
35
+ # @since 3.1.0
36
+ def each
37
+ if block_given?
38
+ documents.each do |doc|
39
+ yield doc
40
+ end
41
+ else
42
+ to_enum
43
+ end
44
+ end
45
+
46
+ # Provide a distance multiplier to be used for each returned distance.
47
+ #
48
+ # @example Provide the distance multiplier.
49
+ # geo_near.distance_multiplier(13113.1)
50
+ #
51
+ # @param [ Integer, Float ] value The distance multiplier.
52
+ #
53
+ # @return [ GeoNear ] The GeoNear wrapper.
54
+ #
55
+ # @since 3.1.0
56
+ def distance_multiplier(value)
57
+ command[:distanceMultiplier] = value
58
+ self
59
+ end
60
+
61
+ # Initialize the new map/reduce directive.
62
+ #
63
+ # @example Initialize the new map/reduce.
64
+ # MapReduce.new(criteria, map, reduce)
65
+ #
66
+ # @param [ Criteria ] criteria The Mongoid criteria.
67
+ # @param [ String ] map The map js function.
68
+ # @param [ String ] reduce The reduce js function.
69
+ #
70
+ # @since 3.0.0
71
+ def initialize(collection, criteria, near)
72
+ @collection, @criteria = collection, criteria
73
+ command[:geoNear] = collection.name.to_s
74
+ command[:near] = near
75
+ apply_criteria_options
76
+ end
77
+
78
+ # Get a pretty string representation of the command.
79
+ #
80
+ # @example Inspect the geoNear.
81
+ # geo_near.inspect
82
+ #
83
+ # @return [ String ] The inspection string.
84
+ #
85
+ # @since 3.1.0
86
+ def inspect
87
+ %Q{#<Mongoid::Contextual::GeoNear
88
+ selector: #{criteria.selector.inspect}
89
+ class: #{criteria.klass}
90
+ near: #{command[:near]}
91
+ multiplier: #{command[:distanceMultiplier] || "N/A"}
92
+ max: #{command[:maxDistance] || "N/A"}
93
+ unique: #{command[:unique].nil? ? true : command[:unique]}
94
+ spherical: #{command[:spherical] || false}>
95
+ }
96
+ end
97
+
98
+ # Specify the maximum distance to find documents for, or get the value of
99
+ # the document with the furthest distance.
100
+ #
101
+ # @example Set the max distance.
102
+ # geo_near.max_distance(0.5)
103
+ #
104
+ # @example Get the max distance.
105
+ # geo_near.max_distance
106
+ #
107
+ # @param [ Integer, Float ] value The maximum distance.
108
+ #
109
+ # @return [ GeoNear, Float ] The GeoNear command or the value.
110
+ #
111
+ # @since 3.1.0
112
+ def max_distance(value = nil)
113
+ if value
114
+ command[:maxDistance] = value
115
+ self
116
+ else
117
+ stats["maxDistance"]
118
+ end
119
+ end
120
+
121
+ # Tell the command to calculate based on spherical distances.
122
+ #
123
+ # @example Add the spherical flag.
124
+ # geo_near.spherical
125
+ #
126
+ # @return [ GeoNear ] The command.
127
+ #
128
+ # @since 3.1.0
129
+ def spherical
130
+ command[:spherical] = true
131
+ self
132
+ end
133
+
134
+ # Tell the command whether or not the retured results should be unique.
135
+ #
136
+ # @example Set the unique flag.
137
+ # geo_near.unique(false)
138
+ #
139
+ # @param [ true, false ] value Whether to return unique documents.
140
+ #
141
+ # @return [ GeoNear ] The command.
142
+ #
143
+ # @since 3.1.0
144
+ def unique(value = true)
145
+ command[:unique] = value
146
+ self
147
+ end
148
+
149
+ # Execute the $geoNear, returning the raw output.
150
+ #
151
+ # @example Run the $geoNear
152
+ # geo_near.execute
153
+ #
154
+ # @return [ Hash ] The raw output
155
+ #
156
+ # @since 3.1.0
157
+ def execute
158
+ results
159
+ end
160
+
161
+ # Get the stats for the command run.
162
+ #
163
+ # @example Get the stats.
164
+ # geo_near.stats
165
+ #
166
+ # @return [ Hash ] The stats from the command run.
167
+ #
168
+ # @since 3.1.0
169
+ def stats
170
+ results["stats"]
171
+ end
172
+
173
+ # Get the execution time of the command.
174
+ #
175
+ # @example Get the execution time.
176
+ # geo_near.time
177
+ #
178
+ # @return [ Float ] The execution time.
179
+ #
180
+ # @since 3.1.0
181
+ def time
182
+ stats["time"]
183
+ end
184
+
185
+ private
186
+
187
+ # Apply criteria specific options - query, limit.
188
+ #
189
+ # @api private
190
+ #
191
+ # @example Apply the criteria options
192
+ # geo_near.apply_criteria_options
193
+ #
194
+ # @return [ nil ] Nothing.
195
+ #
196
+ # @since 3.0.0
197
+ def apply_criteria_options
198
+ command[:query] = criteria.selector
199
+ if limit = criteria.options[:limit]
200
+ command[:num] = limit
201
+ end
202
+ end
203
+
204
+ # Get the result documents from the $geoNear.
205
+ #
206
+ # @api private
207
+ #
208
+ # @example Get the documents.
209
+ # geo_near.documents
210
+ #
211
+ # @return [ Array, Cursor ] The documents.
212
+ #
213
+ # @since 3.0.0
214
+ def documents
215
+ results["results"].map do |attributes|
216
+ doc = Factory.from_db(criteria.klass, attributes["obj"], criteria.object_id)
217
+ doc.attributes["geo_near_distance"] = attributes["dis"]
218
+ doc
219
+ end
220
+ end
221
+
222
+ # Execute the $geoNear command and get the results.
223
+ #
224
+ # @api private
225
+ #
226
+ # @example Get the results.
227
+ # geo_near.results
228
+ #
229
+ # @return [ Hash ] The results of the command.
230
+ #
231
+ # @since 3.0.0
232
+ def results
233
+ @results ||= session.command(command)
234
+ end
235
+ end
236
+ end
237
+ end
238
+
@@ -6,7 +6,7 @@ module Mongoid
6
6
  include Command
7
7
 
8
8
  delegate :[], to: :results
9
- delegate :==, :empty?, :inspect, to: :entries
9
+ delegate :==, :empty?, to: :entries
10
10
 
11
11
  # Get all the counts returned by the map/reduce.
12
12
  #
@@ -162,6 +162,17 @@ module Mongoid
162
162
  results
163
163
  end
164
164
 
165
+ # Execute the map/reduce, returning the raw output.
166
+ # Useful when you don't care about map/reduce's ouptut.
167
+ #
168
+ # @example Run the map reduce
169
+ # map_reduce.execute
170
+ #
171
+ # @return [ Hash ] The raw output
172
+ #
173
+ # @since 3.1.0
174
+ alias :execute :raw
175
+
165
176
  # Get the number of documents reduced by the map/reduce.
166
177
  #
167
178
  # @example Get the reduced document count.
@@ -6,22 +6,15 @@ module Mongoid
6
6
  class Memory
7
7
  include Enumerable
8
8
  include Aggregable::Memory
9
+ include Eager
10
+ include Queryable
11
+ include Mongoid::Atomic::Positionable
9
12
 
10
- # @attribute [r] collection The root collection.
11
- # @attribute [r] criteria The criteria for the context.
12
- # @attribute [r] klass The criteria class.
13
13
  # @attribute [r] root The root document.
14
14
  # @attribute [r] path The atomic path.
15
15
  # @attribute [r] selector The root document selector.
16
16
  # @attribute [r] matching The in memory documents that match the selector.
17
- attr_reader \
18
- :collection,
19
- :criteria,
20
- :documents,
21
- :klass,
22
- :path,
23
- :root,
24
- :selector
17
+ attr_reader :documents, :path, :root, :selector
25
18
 
26
19
  # Check if the context is equal to the other object.
27
20
  #
@@ -38,19 +31,6 @@ module Mongoid
38
31
  entries == other.entries
39
32
  end
40
33
 
41
- # Is the enumerable of matching documents empty?
42
- #
43
- # @example Is the context empty?
44
- # context.blank?
45
- #
46
- # @return [ true, false ] If the context is empty.
47
- #
48
- # @since 3.0.0
49
- def blank?
50
- count == 0
51
- end
52
- alias :empty? :blank?
53
-
54
34
  # Delete all documents in the database that match the selector.
55
35
  #
56
36
  # @example Delete all the documents.
@@ -66,7 +46,9 @@ module Mongoid
66
46
  doc.as_document
67
47
  end
68
48
  unless removed.empty?
69
- collection.find(selector).update("$pullAll" => { path => removed })
49
+ collection.find(selector).update(
50
+ positionally(selector, "$pullAll" => { path => removed })
51
+ )
70
52
  end
71
53
  deleted
72
54
  end
@@ -117,9 +99,10 @@ module Mongoid
117
99
  # @since 3.0.0
118
100
  def each
119
101
  if block_given?
120
- (documents[skipping || 0, limiting || documents.length] || []).each do |doc|
121
- yield doc
102
+ documents_for_iteration.each do |doc|
103
+ yield(doc)
122
104
  end
105
+ # eager_loadable? ? docs : self
123
106
  else
124
107
  to_enum
125
108
  end
@@ -146,7 +129,9 @@ module Mongoid
146
129
  #
147
130
  # @since 3.0.0
148
131
  def first
149
- documents.first
132
+ doc = documents.first
133
+ eager_load_one(doc) if eager_loadable?(doc)
134
+ doc
150
135
  end
151
136
  alias :one :first
152
137
 
@@ -178,7 +163,9 @@ module Mongoid
178
163
  #
179
164
  # @since 3.0.0
180
165
  def last
181
- documents.last
166
+ doc = documents.last
167
+ eager_load_one(doc) if eager_loadable?(doc)
168
+ doc
182
169
  end
183
170
 
184
171
  # Get the length of matching documents in the context.
@@ -269,6 +256,24 @@ module Mongoid
269
256
 
270
257
  private
271
258
 
259
+ # Get the documents the context should iterate. This follows 3 rules:
260
+ #
261
+ # @api private
262
+ #
263
+ # @example Get the documents for iteration.
264
+ # context.documents_for_iteration
265
+ #
266
+ # @return [ Array<Document> ] The docs to iterate.
267
+ #
268
+ # @since 3.1.0
269
+ def documents_for_iteration
270
+ docs = documents[skipping || 0, limiting || documents.length] || []
271
+ if eager_loadable?
272
+ eager_load(docs)
273
+ end
274
+ docs
275
+ end
276
+
272
277
  # Update the provided documents with the attributes.
273
278
  #
274
279
  # @api private
@@ -404,11 +409,11 @@ module Mongoid
404
409
  #
405
410
  # @since 3.0.0
406
411
  def in_place_sort(values)
407
- values.each_pair do |field, dir|
412
+ values.keys.reverse.each do |field|
408
413
  documents.sort! do |a, b|
409
414
  a_value, b_value = a[field], b[field]
410
415
  value = compare(a_value.__sortable__, b_value.__sortable__)
411
- dir < 0 ? value * -1 : value
416
+ values[field] < 0 ? value * -1 : value
412
417
  end
413
418
  end
414
419
  end