djsun-mongomapper 0.3.1.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/History +20 -1
  2. data/Rakefile +5 -3
  3. data/VERSION +1 -1
  4. data/lib/mongomapper/associations/base.rb +3 -5
  5. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +5 -3
  6. data/lib/mongomapper/associations/belongs_to_proxy.rb +4 -4
  7. data/lib/mongomapper/associations/many_documents_proxy.rb +32 -14
  8. data/lib/mongomapper/associations/proxy.rb +2 -6
  9. data/lib/mongomapper/associations.rb +38 -15
  10. data/lib/mongomapper/document.rb +165 -95
  11. data/lib/mongomapper/dynamic_finder.rb +38 -0
  12. data/lib/mongomapper/embedded_document.rb +116 -88
  13. data/lib/mongomapper/finder_options.rb +3 -14
  14. data/lib/mongomapper/key.rb +12 -16
  15. data/lib/mongomapper/serializers/json_serializer.rb +15 -12
  16. data/lib/mongomapper/support.rb +30 -0
  17. data/lib/mongomapper.rb +7 -33
  18. data/mongomapper.gemspec +10 -7
  19. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +14 -0
  20. data/test/functional/associations/test_belongs_to_proxy.rb +10 -0
  21. data/test/functional/associations/test_many_polymorphic_proxy.rb +46 -52
  22. data/test/functional/associations/test_many_proxy.rb +71 -12
  23. data/test/functional/test_associations.rb +9 -2
  24. data/test/functional/test_document.rb +281 -20
  25. data/test/functional/test_rails_compatibility.rb +2 -3
  26. data/test/models.rb +39 -8
  27. data/test/unit/serializers/test_json_serializer.rb +46 -12
  28. data/test/unit/test_association_base.rb +10 -2
  29. data/test/unit/test_document.rb +7 -9
  30. data/test/unit/test_embedded_document.rb +180 -24
  31. data/test/unit/test_finder_options.rb +7 -38
  32. data/test/unit/test_key.rb +54 -24
  33. metadata +5 -5
  34. data/test/unit/test_mongo_id.rb +0 -35
@@ -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
- collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
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
- private
158
- def find_every(options)
159
- criteria, options = FinderOptions.new(options).to_a
160
- collection.find(criteria, options).to_a.map { |doc| new(doc) }
161
- end
162
-
163
- def find_first(options)
164
- options.merge!(:limit => 1)
165
- find_every({:order => '$natural asc'}.merge(options))[0]
166
- end
167
-
168
- def find_last(options)
169
- find_every(options.merge(:limit => 1, :order => '$natural desc'))[0]
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
- end
180
-
181
- def find_one(id, options={})
182
- if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
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
- end
188
-
189
- def find_from_ids(ids, options={})
190
- ids = ids.flatten.compact.uniq
191
-
192
- case ids.size
193
- when 0
194
- raise(DocumentNotFound, "Couldn't find without an ID")
195
- when 1
196
- find_one(ids[0], options)
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
- find_some(ids, options)
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
- end
201
-
202
- def update_single(id, attrs)
203
- if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
204
- raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
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
- find(id).update_attributes(attrs)
208
- end
209
-
210
- def update_multiple(docs)
211
- unless docs.is_a?(Hash)
212
- raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
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
- instances = []
216
- docs.each_pair { |id, attrs| instances << update(id, attrs) }
217
- instances
218
- end
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
- update_timestamps
258
- write_attribute :_id, save_to_collection
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
- t = Time.now.utc
273
- write_attribute('created_at', t) if new?
274
- write_attribute('updated_at', t)
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