mongodoc 0.1.2 → 0.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.
- data/README.textile +143 -0
- data/Rakefile +35 -3
- data/VERSION +1 -1
- data/examples/simple_document.rb +35 -0
- data/examples/simple_object.rb +32 -0
- data/features/finders.feature +72 -0
- data/features/mongodoc_base.feature +12 -2
- data/features/named_scopes.feature +66 -0
- data/features/new_record.feature +36 -0
- data/features/partial_updates.feature +105 -0
- data/features/step_definitions/criteria_steps.rb +4 -41
- data/features/step_definitions/document_steps.rb +56 -5
- data/features/step_definitions/documents.rb +14 -3
- data/features/step_definitions/finder_steps.rb +15 -0
- data/features/step_definitions/named_scope_steps.rb +18 -0
- data/features/step_definitions/partial_update_steps.rb +32 -0
- data/features/step_definitions/query_steps.rb +51 -0
- data/features/using_criteria.feature +5 -1
- data/lib/mongodoc/attributes.rb +76 -63
- data/lib/mongodoc/collection.rb +9 -9
- data/lib/mongodoc/criteria.rb +152 -161
- data/lib/mongodoc/cursor.rb +7 -5
- data/lib/mongodoc/document.rb +95 -31
- data/lib/mongodoc/finders.rb +29 -0
- data/lib/mongodoc/named_scope.rb +68 -0
- data/lib/mongodoc/parent_proxy.rb +15 -6
- data/lib/mongodoc/proxy.rb +22 -13
- data/lib/mongodoc.rb +3 -3
- data/mongodoc.gemspec +42 -14
- data/perf/mongodoc_runner.rb +90 -0
- data/perf/ruby_driver_runner.rb +64 -0
- data/spec/attributes_spec.rb +46 -12
- data/spec/collection_spec.rb +23 -23
- data/spec/criteria_spec.rb +124 -187
- data/spec/cursor_spec.rb +21 -17
- data/spec/document_ext.rb +2 -2
- data/spec/document_spec.rb +187 -218
- data/spec/embedded_save_spec.rb +104 -0
- data/spec/finders_spec.rb +81 -0
- data/spec/hash_matchers.rb +27 -0
- data/spec/named_scope_spec.rb +82 -0
- data/spec/new_record_spec.rb +216 -0
- data/spec/parent_proxy_spec.rb +8 -6
- data/spec/proxy_spec.rb +80 -0
- data/spec/spec_helper.rb +2 -0
- metadata +35 -7
- data/README.rdoc +0 -75
data/lib/mongodoc/criteria.rb
CHANGED
@@ -12,7 +12,7 @@ module MongoDoc #:nodoc:
|
|
12
12
|
#
|
13
13
|
# <tt>criteria = Criteria.new</tt>
|
14
14
|
#
|
15
|
-
# <tt>criteria.
|
15
|
+
# <tt>criteria.only(:field => "value").only(:field).skip(20).limit(20)</tt>
|
16
16
|
#
|
17
17
|
# <tt>criteria.execute</tt>
|
18
18
|
class Criteria
|
@@ -25,7 +25,17 @@ module MongoDoc #:nodoc:
|
|
25
25
|
|
26
26
|
include Enumerable
|
27
27
|
|
28
|
-
attr_reader :klass, :options, :selector
|
28
|
+
attr_reader :collection, :klass, :options, :selector
|
29
|
+
|
30
|
+
# Create the new +Criteria+ object. This will initialize the selector
|
31
|
+
# and options hashes, as well as the type of criteria.
|
32
|
+
#
|
33
|
+
# Options:
|
34
|
+
#
|
35
|
+
# klass: The class to execute on.
|
36
|
+
def initialize(klass)
|
37
|
+
@selector, @options, @klass = {}, {}, klass
|
38
|
+
end
|
29
39
|
|
30
40
|
# Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
|
31
41
|
# of this +Criteria+ or the criteria itself.
|
@@ -41,7 +51,7 @@ module MongoDoc #:nodoc:
|
|
41
51
|
self.selector == other.selector && self.options == other.options
|
42
52
|
when Enumerable
|
43
53
|
@collection ||= execute
|
44
|
-
return (
|
54
|
+
return (collection == other)
|
45
55
|
else
|
46
56
|
return false
|
47
57
|
end
|
@@ -55,31 +65,20 @@ module MongoDoc #:nodoc:
|
|
55
65
|
#
|
56
66
|
# Example:
|
57
67
|
#
|
58
|
-
# <tt>criteria.
|
59
|
-
def aggregate
|
60
|
-
|
61
|
-
aggregating_klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE)
|
68
|
+
# <tt>criteria.only(:field1).where(:field1 => "Title").aggregate</tt>
|
69
|
+
def aggregate
|
70
|
+
klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
|
62
71
|
end
|
63
72
|
|
64
|
-
#
|
65
|
-
# be matched in order to return results. Similar to an "in" clause but the
|
66
|
-
# underlying conditional logic is an "AND" and not an "OR". The MongoDB
|
67
|
-
# conditional operator that will be used is "$all".
|
68
|
-
#
|
69
|
-
# Options:
|
70
|
-
#
|
71
|
-
# selections: A +Hash+ where the key is the field name and the value is an
|
72
|
-
# +Array+ of values that must all match.
|
73
|
+
# Get all the matching documents in the database for the +Criteria+.
|
73
74
|
#
|
74
75
|
# Example:
|
75
76
|
#
|
76
|
-
# <tt>criteria.
|
77
|
+
# <tt>criteria.all</tt>
|
77
78
|
#
|
78
|
-
# <tt>
|
79
|
-
|
80
|
-
|
81
|
-
def every(selections = {})
|
82
|
-
selections.each { |key, value| selector[key] = { "$all" => value } }; self
|
79
|
+
# Returns: <tt>Array</tt>
|
80
|
+
def all
|
81
|
+
collect
|
83
82
|
end
|
84
83
|
|
85
84
|
# Get the count of matching documents in the database for the +Criteria+.
|
@@ -102,12 +101,97 @@ module MongoDoc #:nodoc:
|
|
102
101
|
def each(&block)
|
103
102
|
@collection ||= execute
|
104
103
|
if block_given?
|
105
|
-
@collection.
|
106
|
-
|
107
|
-
|
104
|
+
@collection = collection.inject([]) do |container, item|
|
105
|
+
container << item
|
106
|
+
yield item
|
107
|
+
container
|
108
|
+
end
|
109
|
+
end
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
|
114
|
+
# Groups the criteria. This will take the internally built selector and options
|
115
|
+
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
116
|
+
# collection itself will be retrieved from the class provided, and once the
|
117
|
+
# query has returned it will provided a grouping of keys with objects.
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
#
|
121
|
+
# <tt>criteria.only(:field1).where(:field1 => "Title").group</tt>
|
122
|
+
def group
|
123
|
+
klass.collection.group(
|
124
|
+
options[:fields],
|
125
|
+
selector,
|
126
|
+
{ :group => [] },
|
127
|
+
GROUP_REDUCE,
|
128
|
+
true
|
129
|
+
).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return the last result for the +Criteria+. Essentially does a find_one on
|
133
|
+
# the collection with the sorting reversed. If no sorting parameters have
|
134
|
+
# been provided it will default to ids.
|
135
|
+
#
|
136
|
+
# Example:
|
137
|
+
#
|
138
|
+
# <tt>Criteria.only(:name).where(:name = "Chrissy").last</tt>
|
139
|
+
def last
|
140
|
+
opts = options.dup
|
141
|
+
sorting = opts[:sort]
|
142
|
+
sorting = [[:_id, :asc]] unless sorting
|
143
|
+
opts[:sort] = sorting.collect { |option| [ option.first, Criteria.invert(option.last) ] }
|
144
|
+
klass.collection.find_one(selector, opts)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return the first result for the +Criteria+.
|
148
|
+
#
|
149
|
+
# Example:
|
150
|
+
#
|
151
|
+
# <tt>Criteria.only(:name).where(:name = "Chrissy").one</tt>
|
152
|
+
def one
|
153
|
+
klass.collection.find_one(selector, options.dup)
|
154
|
+
end
|
155
|
+
alias :first :one
|
156
|
+
|
157
|
+
# Translate the supplied argument hash
|
158
|
+
#
|
159
|
+
# Options:
|
160
|
+
#
|
161
|
+
# criteria_conditions: Hash of criteria keys, and parameter values
|
162
|
+
#
|
163
|
+
# Example:
|
164
|
+
#
|
165
|
+
# <tt>criteria.criteria(:where => { :field => "value"}, :limit => 20)</tt>
|
166
|
+
#
|
167
|
+
# Returns <tt>self</tt>
|
168
|
+
def criteria(criteria_conditions = {})
|
169
|
+
criteria_conditions.inject(self) do |criteria, (key, value)|
|
170
|
+
criteria.send(key, value)
|
108
171
|
end
|
109
172
|
end
|
110
173
|
|
174
|
+
# Adds a criterion to the +Criteria+ that specifies values that must all
|
175
|
+
# be matched in order to return results. Similar to an "in" clause but the
|
176
|
+
# underlying conditional logic is an "AND" and not an "OR". The MongoDB
|
177
|
+
# conditional operator that will be used is "$all".
|
178
|
+
#
|
179
|
+
# Options:
|
180
|
+
#
|
181
|
+
# selections: A +Hash+ where the key is the field name and the value is an
|
182
|
+
# +Array+ of values that must all match.
|
183
|
+
#
|
184
|
+
# Example:
|
185
|
+
#
|
186
|
+
# <tt>criteria.every(:field => ["value1", "value2"])</tt>
|
187
|
+
#
|
188
|
+
# <tt>criteria.every(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
189
|
+
#
|
190
|
+
# Returns: <tt>self</tt>
|
191
|
+
def every(selections = {})
|
192
|
+
selections.each { |key, value| selector[key] = { "$all" => value } }; self
|
193
|
+
end
|
194
|
+
|
111
195
|
# Adds a criterion to the +Criteria+ that specifies values that are not allowed
|
112
196
|
# to match any document in the database. The MongoDB conditional operator that
|
113
197
|
# will be used is "$ne".
|
@@ -146,32 +230,19 @@ module MongoDoc #:nodoc:
|
|
146
230
|
self
|
147
231
|
end
|
148
232
|
|
149
|
-
#
|
233
|
+
# Adds a criterion to the +Criteria+ that specifies an id that must be matched.
|
150
234
|
#
|
151
|
-
#
|
235
|
+
# Options:
|
152
236
|
#
|
153
|
-
# <tt>
|
154
|
-
def one
|
155
|
-
klass.collection.find_one(selector, options.dup)
|
156
|
-
end
|
157
|
-
alias :first :one
|
158
|
-
|
159
|
-
GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
|
160
|
-
# Groups the criteria. This will take the internally built selector and options
|
161
|
-
# and pass them on to the Ruby driver's +group()+ method on the collection. The
|
162
|
-
# collection itself will be retrieved from the class provided, and once the
|
163
|
-
# query has returned it will provided a grouping of keys with objects.
|
237
|
+
# id_or_object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
|
164
238
|
#
|
165
239
|
# Example:
|
166
240
|
#
|
167
|
-
# <tt>criteria.
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
{ :group => [] },
|
173
|
-
GROUP_REDUCE
|
174
|
-
).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
|
241
|
+
# <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
|
242
|
+
#
|
243
|
+
# Returns: <tt>self</tt>
|
244
|
+
def id(id_or_object_id)
|
245
|
+
selector[:_id] = id_or_object_id; self
|
175
246
|
end
|
176
247
|
|
177
248
|
# Adds a criterion to the +Criteria+ that specifies values where any can
|
@@ -194,47 +265,6 @@ module MongoDoc #:nodoc:
|
|
194
265
|
inclusions.each { |key, value| selector[key] = { "$in" => value } }; self
|
195
266
|
end
|
196
267
|
|
197
|
-
# Adds a criterion to the +Criteria+ that specifies an id that must be matched.
|
198
|
-
#
|
199
|
-
# Options:
|
200
|
-
#
|
201
|
-
# object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
|
202
|
-
#
|
203
|
-
# Example:
|
204
|
-
#
|
205
|
-
# <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
|
206
|
-
#
|
207
|
-
# Returns: <tt>self</tt>
|
208
|
-
def id(object_id)
|
209
|
-
selector[:_id] = object_id; self
|
210
|
-
end
|
211
|
-
|
212
|
-
# Create the new +Criteria+ object. This will initialize the selector
|
213
|
-
# and options hashes, as well as the type of criteria.
|
214
|
-
#
|
215
|
-
# Options:
|
216
|
-
#
|
217
|
-
# type: One of :all, :first:, or :last
|
218
|
-
# klass: The class to execute on.
|
219
|
-
def initialize(klass)
|
220
|
-
@selector, @options, @klass = {}, {}, klass
|
221
|
-
end
|
222
|
-
|
223
|
-
# Return the last result for the +Criteria+. Essentially does a find_one on
|
224
|
-
# the collection with the sorting reversed. If no sorting parameters have
|
225
|
-
# been provided it will default to ids.
|
226
|
-
#
|
227
|
-
# Example:
|
228
|
-
#
|
229
|
-
# <tt>Criteria.select(:name).where(:name = "Chrissy").last</tt>
|
230
|
-
def last
|
231
|
-
opts = options.dup
|
232
|
-
sorting = opts[:sort]
|
233
|
-
sorting = [[:_id, :asc]] unless sorting
|
234
|
-
opts[:sort] = sorting.collect { |option| [ option.first, Criteria.invert(option.last) ] }
|
235
|
-
klass.collection.find_one(selector, opts)
|
236
|
-
end
|
237
|
-
|
238
268
|
# Adds a criterion to the +Criteria+ that specifies the maximum number of
|
239
269
|
# results to return. This is mostly used in conjunction with <tt>skip()</tt>
|
240
270
|
# to handle paginated results.
|
@@ -252,56 +282,6 @@ module MongoDoc #:nodoc:
|
|
252
282
|
options[:limit] = value; self
|
253
283
|
end
|
254
284
|
|
255
|
-
# Merges another object into this +Criteria+. The other object may be a
|
256
|
-
# +Criteria+ or a +Hash+. This is used to combine multiple scopes together,
|
257
|
-
# where a chained scope situation may be desired.
|
258
|
-
#
|
259
|
-
# Options:
|
260
|
-
#
|
261
|
-
# other: The +Criteria+ or +Hash+ to merge with.
|
262
|
-
#
|
263
|
-
# Example:
|
264
|
-
#
|
265
|
-
# <tt>criteria.merge({ :conditions => { :title => "Sir" } })</tt>
|
266
|
-
def merge(other)
|
267
|
-
selector.update(other.selector)
|
268
|
-
options.update(other.options)
|
269
|
-
end
|
270
|
-
|
271
|
-
# Used for chaining +Criteria+ scopes together in the for of class methods
|
272
|
-
# on the +Document+ the criteria is for.
|
273
|
-
#
|
274
|
-
# Options:
|
275
|
-
#
|
276
|
-
# name: The name of the class method on the +Document+ to chain.
|
277
|
-
# args: The arguments passed to the method.
|
278
|
-
#
|
279
|
-
# Example:
|
280
|
-
#
|
281
|
-
# class Person < Mongoid::Document
|
282
|
-
# field :title
|
283
|
-
# field :terms, :type => Boolean, :default => false
|
284
|
-
#
|
285
|
-
# class << self
|
286
|
-
# def knights
|
287
|
-
# all(:conditions => { :title => "Sir" })
|
288
|
-
# end
|
289
|
-
#
|
290
|
-
# def accepted
|
291
|
-
# all(:conditions => { :terms => true })
|
292
|
-
# end
|
293
|
-
# end
|
294
|
-
# end
|
295
|
-
#
|
296
|
-
# Person.accepted.knights #returns a merged criteria of the 2 scopes.
|
297
|
-
#
|
298
|
-
# Returns: <tt>Criteria</tt>
|
299
|
-
def method_missing(name, *args)
|
300
|
-
new_scope = klass.send(name)
|
301
|
-
new_scope.merge(self)
|
302
|
-
new_scope
|
303
|
-
end
|
304
|
-
|
305
285
|
# Adds a criterion to the +Criteria+ that specifies values where none
|
306
286
|
# should match in order to return results. This is similar to an SQL "NOT IN"
|
307
287
|
# clause. The MongoDB conditional operator that will be used is "$nin".
|
@@ -363,7 +343,7 @@ module MongoDoc #:nodoc:
|
|
363
343
|
def paginate
|
364
344
|
@collection ||= execute
|
365
345
|
WillPaginate::Collection.create(page, per_page, count) do |pager|
|
366
|
-
pager.replace(
|
346
|
+
pager.replace(collection.to_a)
|
367
347
|
end
|
368
348
|
end
|
369
349
|
|
@@ -382,10 +362,10 @@ module MongoDoc #:nodoc:
|
|
382
362
|
#
|
383
363
|
# Example:
|
384
364
|
#
|
385
|
-
# <tt>criteria.
|
365
|
+
# <tt>criteria.only(:field1, :field2, :field3)</tt>
|
386
366
|
#
|
387
367
|
# Returns: <tt>self</tt>
|
388
|
-
def
|
368
|
+
def only(*args)
|
389
369
|
options[:fields] = args.flatten if args.any?; self
|
390
370
|
end
|
391
371
|
|
@@ -407,6 +387,36 @@ module MongoDoc #:nodoc:
|
|
407
387
|
options[:skip] = value; self
|
408
388
|
end
|
409
389
|
|
390
|
+
# Adds a criterion to the +Criteria+ that specifies values that must
|
391
|
+
# be matched in order to return results. This is similar to a SQL "WHERE"
|
392
|
+
# clause. This is the actual selector that will be provided to MongoDB,
|
393
|
+
# similar to the Javascript object that is used when performing a find()
|
394
|
+
# in the MongoDB console.
|
395
|
+
#
|
396
|
+
# Options:
|
397
|
+
#
|
398
|
+
# selector_or_js: A +Hash+ that must match the attributes of the +Document+
|
399
|
+
# or a +String+ of js code.
|
400
|
+
#
|
401
|
+
# Example:
|
402
|
+
#
|
403
|
+
# <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
|
404
|
+
#
|
405
|
+
# <tt>criteria.where('this.a > 3')</tt>
|
406
|
+
#
|
407
|
+
# Returns: <tt>self</tt>
|
408
|
+
def where(selector_or_js = {})
|
409
|
+
case selector_or_js
|
410
|
+
when String
|
411
|
+
selector['$where'] = selector_or_js
|
412
|
+
else
|
413
|
+
selector.merge!(selector_or_js)
|
414
|
+
end
|
415
|
+
self
|
416
|
+
end
|
417
|
+
alias :and :where
|
418
|
+
alias :conditions :where
|
419
|
+
|
410
420
|
# Translate the supplied arguments into a +Criteria+ object.
|
411
421
|
#
|
412
422
|
# If the passed in args is a single +String+, then it will
|
@@ -427,27 +437,8 @@ module MongoDoc #:nodoc:
|
|
427
437
|
#
|
428
438
|
# Returns a new +Criteria+ object.
|
429
439
|
def self.translate(klass, params = {})
|
430
|
-
return new(klass).id(params).one
|
431
|
-
return new(klass).
|
432
|
-
end
|
433
|
-
|
434
|
-
# Adds a criterion to the +Criteria+ that specifies values that must
|
435
|
-
# be matched in order to return results. This is similar to a SQL "WHERE"
|
436
|
-
# clause. This is the actual selector that will be provided to MongoDB,
|
437
|
-
# similar to the Javascript object that is used when performing a find()
|
438
|
-
# in the MongoDB console.
|
439
|
-
#
|
440
|
-
# Options:
|
441
|
-
#
|
442
|
-
# selectior: A +Hash+ that must match the attributes of the +Document+.
|
443
|
-
#
|
444
|
-
# Example:
|
445
|
-
#
|
446
|
-
# <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
|
447
|
-
#
|
448
|
-
# Returns: <tt>self</tt>
|
449
|
-
def where(add_selector = {})
|
450
|
-
selector.merge!(add_selector); self
|
440
|
+
return new(klass).id(params).one unless params.is_a?(Hash)
|
441
|
+
return new(klass).criteria(params)
|
451
442
|
end
|
452
443
|
|
453
444
|
protected
|
data/lib/mongodoc/cursor.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module MongoDoc
|
2
2
|
class Cursor
|
3
|
+
include Enumerable
|
4
|
+
|
3
5
|
attr_accessor :_cursor
|
4
6
|
delegate :close, :closed?, :count, :explain, :limit, :query_options_hash, :query_opts, :skip, :sort, :to => :_cursor
|
5
7
|
|
@@ -8,17 +10,17 @@ module MongoDoc
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def each
|
11
|
-
_cursor.each do |
|
12
|
-
yield MongoDoc::BSON.decode(
|
13
|
+
_cursor.each do |next_document|
|
14
|
+
yield MongoDoc::BSON.decode(next_document)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
MongoDoc::BSON.decode(_cursor.
|
18
|
+
def next_document
|
19
|
+
MongoDoc::BSON.decode(_cursor.next_document)
|
18
20
|
end
|
19
21
|
|
20
22
|
def to_a
|
21
23
|
MongoDoc::BSON.decode(_cursor.to_a)
|
22
24
|
end
|
23
25
|
end
|
24
|
-
end
|
26
|
+
end
|
data/lib/mongodoc/document.rb
CHANGED
@@ -2,18 +2,27 @@ require 'mongodoc/bson'
|
|
2
2
|
require 'mongodoc/query'
|
3
3
|
require 'mongodoc/attributes'
|
4
4
|
require 'mongodoc/criteria'
|
5
|
+
require 'mongodoc/finders'
|
6
|
+
require 'mongodoc/named_scope'
|
5
7
|
|
6
8
|
module MongoDoc
|
7
9
|
class DocumentInvalidError < RuntimeError; end
|
8
10
|
class NotADocumentError < RuntimeError; end
|
9
11
|
|
10
|
-
|
11
|
-
extend MongoDoc::Attributes
|
12
|
-
include Validatable
|
12
|
+
module Document
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def self.included(klass)
|
15
|
+
klass.class_eval do
|
16
|
+
include Attributes
|
17
|
+
extend ClassMethods
|
18
|
+
extend Finders
|
19
|
+
extend NamedScope
|
20
|
+
include Validatable
|
21
|
+
|
22
|
+
alias :id :_id
|
23
|
+
alias :to_param :_id
|
24
|
+
end
|
25
|
+
end
|
17
26
|
|
18
27
|
def initialize(attrs = {})
|
19
28
|
self.attributes = attrs
|
@@ -24,6 +33,13 @@ module MongoDoc
|
|
24
33
|
self.class._attributes.all? {|var| self.send(var) == other.send(var)}
|
25
34
|
end
|
26
35
|
|
36
|
+
def attributes
|
37
|
+
self.class._attributes.inject({}) do |hash, attr|
|
38
|
+
hash[attr] = send(attr)
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
27
43
|
def attributes=(attrs)
|
28
44
|
attrs.each do |key, value|
|
29
45
|
send("#{key}=", value)
|
@@ -39,7 +55,7 @@ module MongoDoc
|
|
39
55
|
return _save(false) unless validate and not valid?
|
40
56
|
false
|
41
57
|
end
|
42
|
-
|
58
|
+
|
43
59
|
def save!
|
44
60
|
return _root.save! if _root
|
45
61
|
raise DocumentInvalidError unless valid?
|
@@ -56,18 +72,28 @@ module MongoDoc
|
|
56
72
|
end
|
57
73
|
|
58
74
|
def update_attributes(attrs)
|
75
|
+
strict = attrs.delete(:__strict__)
|
59
76
|
self.attributes = attrs
|
60
|
-
return
|
61
|
-
|
77
|
+
return false unless valid?
|
78
|
+
if strict
|
79
|
+
_strict_update_attributes(_path_to_root(self, attrs), false)
|
80
|
+
else
|
81
|
+
_naive_update_attributes(_path_to_root(self, attrs), false)
|
82
|
+
end
|
62
83
|
end
|
63
84
|
|
64
85
|
def update_attributes!(attrs)
|
86
|
+
strict = attrs.delete(:__strict__)
|
65
87
|
self.attributes = attrs
|
66
88
|
raise DocumentInvalidError unless valid?
|
67
|
-
|
89
|
+
if strict
|
90
|
+
_strict_update_attributes(_path_to_root(self, attrs), true)
|
91
|
+
else
|
92
|
+
_naive_update_attributes(_path_to_root(self, attrs), true)
|
93
|
+
end
|
68
94
|
end
|
69
95
|
|
70
|
-
|
96
|
+
module ClassMethods
|
71
97
|
def bson_create(bson_hash, options = {})
|
72
98
|
new.tap do |obj|
|
73
99
|
bson_hash.each do |name, value|
|
@@ -84,16 +110,12 @@ module MongoDoc
|
|
84
110
|
self.to_s.tableize.gsub('/', '.')
|
85
111
|
end
|
86
112
|
|
87
|
-
def count
|
88
|
-
collection.count
|
89
|
-
end
|
90
|
-
|
91
113
|
def create(attrs = {})
|
92
114
|
instance = new(attrs)
|
93
115
|
_create(instance, false) if instance.valid?
|
94
116
|
instance
|
95
117
|
end
|
96
|
-
|
118
|
+
|
97
119
|
def create!(attrs = {})
|
98
120
|
instance = new(attrs)
|
99
121
|
raise MongoDoc::DocumentInvalidError unless instance.valid?
|
@@ -101,12 +123,16 @@ module MongoDoc
|
|
101
123
|
instance
|
102
124
|
end
|
103
125
|
|
104
|
-
|
105
|
-
Criteria.new(self)
|
106
|
-
end
|
126
|
+
protected
|
107
127
|
|
108
|
-
def
|
109
|
-
|
128
|
+
def _create(instance, safe)
|
129
|
+
instance.send(:notify_before_save_observers)
|
130
|
+
instance._id = collection.insert(instance, :safe => safe)
|
131
|
+
instance.send(:notify_save_success_observers)
|
132
|
+
instance._id
|
133
|
+
rescue Mongo::MongoDBError => e
|
134
|
+
instance.send(:notify_save_failed_observers)
|
135
|
+
raise e
|
110
136
|
end
|
111
137
|
end
|
112
138
|
|
@@ -115,24 +141,62 @@ module MongoDoc
|
|
115
141
|
def _collection
|
116
142
|
self.class.collection
|
117
143
|
end
|
118
|
-
|
119
|
-
def
|
120
|
-
return
|
121
|
-
|
144
|
+
|
145
|
+
def _naive_update_attributes(attrs, safe)
|
146
|
+
return _root.send(:_naive_update_attributes, attrs, safe) if _root
|
147
|
+
_collection.update({'_id' => self._id}, MongoDoc::Query.set_modifier(attrs), :safe => safe)
|
148
|
+
end
|
149
|
+
|
150
|
+
def _strict_update_attributes(attrs, safe, selector = {})
|
151
|
+
return _root.send(:_strict_update_attributes, attrs, safe, _selector_path_to_root('_id' => _id)) if _root
|
152
|
+
_collection.update({'_id' => _id}.merge(selector), MongoDoc::Query.set_modifier(attrs), :safe => safe)
|
122
153
|
end
|
123
154
|
|
124
155
|
def _save(safe)
|
156
|
+
notify_before_save_observers
|
125
157
|
self._id = _collection.save(self, :safe => safe)
|
158
|
+
notify_save_success_observers
|
159
|
+
self._id
|
160
|
+
rescue Mongo::MongoDBError => e
|
161
|
+
notify_save_failed_observers
|
162
|
+
raise e
|
126
163
|
end
|
127
164
|
|
128
|
-
def
|
129
|
-
|
165
|
+
def before_save_callback(root)
|
166
|
+
self._id = Mongo::ObjectID.new if new_record?
|
130
167
|
end
|
131
168
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
169
|
+
def save_failed_callback(root)
|
170
|
+
self._id = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def save_success_callback(root)
|
174
|
+
root.unregister_save_observer(self)
|
175
|
+
end
|
176
|
+
|
177
|
+
def save_observers
|
178
|
+
@save_observers ||= []
|
179
|
+
end
|
180
|
+
|
181
|
+
def register_save_observer(child)
|
182
|
+
save_observers << child
|
136
183
|
end
|
184
|
+
|
185
|
+
def unregister_save_observer(child)
|
186
|
+
save_observers.delete(child)
|
187
|
+
end
|
188
|
+
|
189
|
+
def notify_before_save_observers
|
190
|
+
save_observers.each {|obs| obs.before_save_callback(self) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def notify_save_success_observers
|
194
|
+
save_observers.each {|obs| obs.save_success_callback(self) }
|
195
|
+
end
|
196
|
+
|
197
|
+
def notify_save_failed_observers
|
198
|
+
save_observers.each {|obs| obs.save_failed_callback(self) }
|
199
|
+
end
|
200
|
+
|
137
201
|
end
|
138
202
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MongoDoc
|
2
|
+
module Finders
|
3
|
+
[:all, :count, :first, :last].each do |name|
|
4
|
+
module_eval <<-RUBY
|
5
|
+
def #{name}
|
6
|
+
Criteria.new(self).#{name}
|
7
|
+
end
|
8
|
+
RUBY
|
9
|
+
end
|
10
|
+
|
11
|
+
def criteria
|
12
|
+
Criteria.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(*args)
|
16
|
+
query = args.extract_options!
|
17
|
+
which = args.first
|
18
|
+
Criteria.translate(self, query).send(which)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_one(conditions_or_id)
|
22
|
+
if Hash === conditions_or_id
|
23
|
+
Criteria.translate(self, conditions_or_id).one
|
24
|
+
else
|
25
|
+
Criteria.translate(self, conditions_or_id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|