djsun-mongomapper 0.3.1.1 → 0.3.3
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/History +20 -1
- data/Rakefile +5 -3
- data/VERSION +1 -1
- data/lib/mongomapper/associations/base.rb +3 -5
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +5 -3
- data/lib/mongomapper/associations/belongs_to_proxy.rb +4 -4
- data/lib/mongomapper/associations/many_documents_proxy.rb +32 -14
- data/lib/mongomapper/associations/proxy.rb +2 -6
- data/lib/mongomapper/associations.rb +38 -15
- data/lib/mongomapper/document.rb +165 -95
- data/lib/mongomapper/dynamic_finder.rb +38 -0
- data/lib/mongomapper/embedded_document.rb +116 -88
- data/lib/mongomapper/finder_options.rb +3 -14
- data/lib/mongomapper/key.rb +12 -16
- data/lib/mongomapper/serializers/json_serializer.rb +15 -12
- data/lib/mongomapper/support.rb +30 -0
- data/lib/mongomapper.rb +7 -33
- data/mongomapper.gemspec +10 -7
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +14 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +10 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +46 -52
- data/test/functional/associations/test_many_proxy.rb +71 -12
- data/test/functional/test_associations.rb +9 -2
- data/test/functional/test_document.rb +281 -20
- data/test/functional/test_rails_compatibility.rb +2 -3
- data/test/models.rb +39 -8
- data/test/unit/serializers/test_json_serializer.rb +46 -12
- data/test/unit/test_association_base.rb +10 -2
- data/test/unit/test_document.rb +7 -9
- data/test/unit/test_embedded_document.rb +180 -24
- data/test/unit/test_finder_options.rb +7 -38
- data/test/unit/test_key.rb +54 -24
- metadata +5 -5
- data/test/unit/test_mongo_id.rb +0 -35
data/lib/mongomapper/document.rb
CHANGED
@@ -11,18 +11,15 @@ module MongoMapper
|
|
11
11
|
include SaveWithValidation
|
12
12
|
include RailsCompatibility::Document
|
13
13
|
extend ClassMethods
|
14
|
-
|
15
|
-
key :created_at, Time
|
16
|
-
key :updated_at, Time
|
17
14
|
end
|
18
|
-
|
15
|
+
|
19
16
|
descendants << model
|
20
17
|
end
|
21
18
|
|
22
19
|
def self.descendants
|
23
20
|
@descendants ||= Set.new
|
24
21
|
end
|
25
|
-
|
22
|
+
|
26
23
|
module ClassMethods
|
27
24
|
def find(*args)
|
28
25
|
options = args.extract_options!
|
@@ -35,16 +32,15 @@ module MongoMapper
|
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
38
|
-
def paginate(options)
|
35
|
+
def paginate(options)
|
39
36
|
per_page = options.delete(:per_page)
|
40
37
|
page = options.delete(:page)
|
41
38
|
total_entries = count(options[:conditions] || {})
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
options[:limit] = collection.limit
|
39
|
+
collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
|
40
|
+
|
41
|
+
options[:limit] = collection.limit
|
46
42
|
options[:offset] = collection.offset
|
47
|
-
|
43
|
+
|
48
44
|
collection.subject = find_every(options)
|
49
45
|
collection
|
50
46
|
end
|
@@ -67,11 +63,11 @@ module MongoMapper
|
|
67
63
|
new(doc)
|
68
64
|
end
|
69
65
|
end
|
70
|
-
|
66
|
+
|
71
67
|
def count(conditions={})
|
72
68
|
collection.count(FinderOptions.to_mongo_criteria(conditions))
|
73
69
|
end
|
74
|
-
|
70
|
+
|
75
71
|
def create(*docs)
|
76
72
|
instances = []
|
77
73
|
docs = [{}] if docs.blank?
|
@@ -81,7 +77,7 @@ module MongoMapper
|
|
81
77
|
end
|
82
78
|
instances.size == 1 ? instances[0] : instances
|
83
79
|
end
|
84
|
-
|
80
|
+
|
85
81
|
# For updating single document
|
86
82
|
# Person.update(1, {:foo => 'bar'})
|
87
83
|
#
|
@@ -96,25 +92,25 @@ module MongoMapper
|
|
96
92
|
update_single(id, attributes)
|
97
93
|
end
|
98
94
|
end
|
99
|
-
|
95
|
+
|
100
96
|
def delete(*ids)
|
101
97
|
criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
|
102
98
|
collection.remove(criteria)
|
103
99
|
end
|
104
|
-
|
100
|
+
|
105
101
|
def delete_all(conditions={})
|
106
102
|
criteria = FinderOptions.to_mongo_criteria(conditions)
|
107
103
|
collection.remove(criteria)
|
108
104
|
end
|
109
|
-
|
105
|
+
|
110
106
|
def destroy(*ids)
|
111
107
|
find_some(ids.flatten).each(&:destroy)
|
112
108
|
end
|
113
|
-
|
109
|
+
|
114
110
|
def destroy_all(conditions={})
|
115
111
|
find(:all, :conditions => conditions).each(&:destroy)
|
116
112
|
end
|
117
|
-
|
113
|
+
|
118
114
|
def connection(mongo_connection=nil)
|
119
115
|
if mongo_connection.nil?
|
120
116
|
@connection ||= MongoMapper.connection
|
@@ -123,7 +119,7 @@ module MongoMapper
|
|
123
119
|
end
|
124
120
|
@connection
|
125
121
|
end
|
126
|
-
|
122
|
+
|
127
123
|
def database(name=nil)
|
128
124
|
if name.nil?
|
129
125
|
@database ||= MongoMapper.database
|
@@ -132,7 +128,7 @@ module MongoMapper
|
|
132
128
|
end
|
133
129
|
@database
|
134
130
|
end
|
135
|
-
|
131
|
+
|
136
132
|
def collection(name=nil)
|
137
133
|
if name.nil?
|
138
134
|
@collection ||= database.collection(self.to_s.demodulize.tableize)
|
@@ -142,95 +138,158 @@ module MongoMapper
|
|
142
138
|
@collection
|
143
139
|
end
|
144
140
|
|
141
|
+
def timestamps!
|
142
|
+
key :created_at, Time
|
143
|
+
key :updated_at, Time
|
144
|
+
|
145
|
+
class_eval { before_save :update_timestamps }
|
146
|
+
end
|
147
|
+
|
145
148
|
def validates_uniqueness_of(*args)
|
146
149
|
add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
|
147
150
|
end
|
148
|
-
|
151
|
+
|
149
152
|
def validates_exclusion_of(*args)
|
150
153
|
add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
|
151
154
|
end
|
152
|
-
|
155
|
+
|
153
156
|
def validates_inclusion_of(*args)
|
154
157
|
add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
|
155
158
|
end
|
156
159
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
171
|
-
|
172
|
-
def find_some(ids, options={})
|
173
|
-
documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
|
174
|
-
if ids.size == documents.size
|
175
|
-
documents
|
176
|
-
else
|
177
|
-
raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
|
160
|
+
protected
|
161
|
+
def method_missing(method, *args)
|
162
|
+
finder = DynamicFinder.new(self, method)
|
163
|
+
|
164
|
+
if finder.valid?
|
165
|
+
meta_def(finder.options[:method]) do |*args|
|
166
|
+
find_with_args(args, finder.options)
|
167
|
+
end
|
168
|
+
|
169
|
+
send(finder.options[:method], *args)
|
170
|
+
else
|
171
|
+
super
|
172
|
+
end
|
178
173
|
end
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
doc
|
184
|
-
else
|
185
|
-
raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
|
174
|
+
|
175
|
+
private
|
176
|
+
def find_every(options)
|
177
|
+
criteria, options = FinderOptions.new(options).to_a
|
178
|
+
collection.find(criteria, options).to_a.map { |doc| new(doc) }
|
186
179
|
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
180
|
+
|
181
|
+
def find_first(options)
|
182
|
+
options.merge!(:limit => 1)
|
183
|
+
find_every({:order => '$natural asc'}.merge(options))[0]
|
184
|
+
end
|
185
|
+
|
186
|
+
def find_last(options)
|
187
|
+
options.merge!(:limit => 1)
|
188
|
+
options[:order] = invert_order_clause(options)
|
189
|
+
find_every(options)[0]
|
190
|
+
#find_every({:order => '$natural desc'}.merge(invert_order_clause(options)))[0]
|
191
|
+
end
|
192
|
+
|
193
|
+
def invert_order_clause(options)
|
194
|
+
return '$natural desc' unless options[:order]
|
195
|
+
options[:order].split(',').map do |order_segment|
|
196
|
+
if order_segment =~ /\sasc/i
|
197
|
+
order_segment.sub /\sasc/i, ' desc'
|
198
|
+
elsif order_segment =~ /\sdesc/i
|
199
|
+
order_segment.sub /\sdesc/i, ' asc'
|
200
|
+
else
|
201
|
+
"#{order_segment.strip} desc"
|
202
|
+
end
|
203
|
+
end.join(',')
|
204
|
+
end
|
205
|
+
|
206
|
+
def find_some(ids, options={})
|
207
|
+
documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
|
208
|
+
if ids.size == documents.size
|
209
|
+
documents
|
197
210
|
else
|
198
|
-
|
211
|
+
raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
|
212
|
+
end
|
199
213
|
end
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
214
|
+
|
215
|
+
def find_one(id, options={})
|
216
|
+
if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
|
217
|
+
doc
|
218
|
+
else
|
219
|
+
raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
|
220
|
+
end
|
205
221
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
222
|
+
|
223
|
+
def find_from_ids(ids, options={})
|
224
|
+
ids = ids.flatten.compact.uniq
|
225
|
+
|
226
|
+
case ids.size
|
227
|
+
when 0
|
228
|
+
raise(DocumentNotFound, "Couldn't find without an ID")
|
229
|
+
when 1
|
230
|
+
find_one(ids[0], options)
|
231
|
+
else
|
232
|
+
find_some(ids, options)
|
233
|
+
end
|
213
234
|
end
|
214
235
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
236
|
+
def find_with_args(args, options)
|
237
|
+
attributes, = {}
|
238
|
+
find_options = args.extract_options!.deep_merge(:conditions => attributes)
|
239
|
+
|
240
|
+
options[:attribute_names].each_with_index do |attr, index|
|
241
|
+
attributes[attr] = args[index]
|
242
|
+
end
|
243
|
+
|
244
|
+
result = find(options[:finder], find_options)
|
245
|
+
|
246
|
+
if result.nil?
|
247
|
+
if options[:bang]
|
248
|
+
raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
|
249
|
+
end
|
250
|
+
|
251
|
+
if options[:instantiator]
|
252
|
+
self.send(options[:instantiator], attributes)
|
253
|
+
end
|
254
|
+
else
|
255
|
+
result
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def update_single(id, attrs)
|
260
|
+
if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
|
261
|
+
raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
|
262
|
+
end
|
263
|
+
|
264
|
+
doc = find(id)
|
265
|
+
doc.update_attributes(attrs)
|
266
|
+
doc
|
267
|
+
end
|
268
|
+
|
269
|
+
def update_multiple(docs)
|
270
|
+
unless docs.is_a?(Hash)
|
271
|
+
raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
|
272
|
+
end
|
273
|
+
|
274
|
+
instances = []
|
275
|
+
docs.each_pair { |id, attrs| instances << update(id, attrs) }
|
276
|
+
instances
|
277
|
+
end
|
219
278
|
end
|
220
|
-
|
279
|
+
|
221
280
|
module InstanceMethods
|
222
281
|
def collection
|
223
282
|
self.class.collection
|
224
283
|
end
|
225
|
-
|
284
|
+
|
226
285
|
def new?
|
227
|
-
read_attribute('_id').blank?
|
286
|
+
read_attribute('_id').blank? || using_custom_id?
|
228
287
|
end
|
229
|
-
|
288
|
+
|
230
289
|
def save
|
231
290
|
create_or_update
|
232
291
|
end
|
233
|
-
|
292
|
+
|
234
293
|
def save!
|
235
294
|
create_or_update || raise(DocumentNotValid.new(self))
|
236
295
|
end
|
@@ -238,40 +297,51 @@ module MongoMapper
|
|
238
297
|
def update_attributes(attrs={})
|
239
298
|
self.attributes = attrs
|
240
299
|
save
|
241
|
-
self
|
242
300
|
end
|
243
|
-
|
301
|
+
|
244
302
|
def destroy
|
303
|
+
return false if frozen?
|
304
|
+
|
245
305
|
criteria = FinderOptions.to_mongo_criteria(:_id => id)
|
246
306
|
collection.remove(criteria) unless new?
|
247
307
|
freeze
|
248
308
|
end
|
249
|
-
|
309
|
+
|
250
310
|
private
|
251
311
|
def create_or_update
|
252
312
|
result = new? ? create : update
|
253
313
|
result != false
|
254
314
|
end
|
255
|
-
|
315
|
+
|
256
316
|
def create
|
257
|
-
|
258
|
-
|
317
|
+
assign_id
|
318
|
+
save_to_collection
|
259
319
|
end
|
260
320
|
|
321
|
+
def assign_id
|
322
|
+
if read_attribute(:_id).blank?
|
323
|
+
write_attribute(:_id, XGen::Mongo::Driver::ObjectID.new.to_s)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
261
327
|
def update
|
262
|
-
update_timestamps
|
263
328
|
save_to_collection
|
264
329
|
end
|
265
|
-
|
330
|
+
|
266
331
|
# collection.save returns mongoid
|
267
332
|
def save_to_collection
|
333
|
+
clear_custom_id_flag
|
268
334
|
collection.save(attributes)
|
269
335
|
end
|
270
|
-
|
336
|
+
|
271
337
|
def update_timestamps
|
272
|
-
|
273
|
-
write_attribute('created_at',
|
274
|
-
write_attribute('updated_at',
|
338
|
+
now = Time.now.utc
|
339
|
+
write_attribute('created_at', now) if new?
|
340
|
+
write_attribute('updated_at', now)
|
341
|
+
end
|
342
|
+
|
343
|
+
def clear_custom_id_flag
|
344
|
+
@using_custom_id = nil
|
275
345
|
end
|
276
346
|
end
|
277
347
|
end # Document
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
class DynamicFinder
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def initialize(model, method)
|
6
|
+
@model = model
|
7
|
+
@options = {}
|
8
|
+
@options[:method] = method
|
9
|
+
match
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
@options[:finder].present?
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def match
|
18
|
+
@options[:finder] = :first
|
19
|
+
|
20
|
+
case @options[:method].to_s
|
21
|
+
when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
|
22
|
+
@options[:finder] = :last if $1 == 'last_by'
|
23
|
+
@options[:finder] = :all if $1 == 'all_by'
|
24
|
+
names = $2
|
25
|
+
when /^find_by_([_a-zA-Z]\w*)\!$/
|
26
|
+
@options[:bang] = true
|
27
|
+
names = $1
|
28
|
+
when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
|
29
|
+
@options[:instantiator] = $1 == 'initialize' ? :new : :create
|
30
|
+
names = $2
|
31
|
+
else
|
32
|
+
@options[:finder] = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
@options[:attribute_names] = names && names.split('_and_')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|