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.
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