jmonteiro-mongo_mapper 0.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 (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/jmonteiro-mongo_mapper.gemspec +195 -0
  8. data/lib/mongo_mapper.rb +128 -0
  9. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  10. data/lib/mongo_mapper/document.rb +402 -0
  11. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  12. data/lib/mongo_mapper/embedded_document.rb +61 -0
  13. data/lib/mongo_mapper/finder_options.rb +127 -0
  14. data/lib/mongo_mapper/plugins.rb +19 -0
  15. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  16. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  20. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
  21. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  23. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  25. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  26. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  27. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
  28. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  29. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  30. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  31. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  32. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  33. data/lib/mongo_mapper/plugins/equality.rb +11 -0
  34. data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
  35. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  36. data/lib/mongo_mapper/plugins/keys.rb +295 -0
  37. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  38. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  39. data/lib/mongo_mapper/plugins/protected.rb +31 -0
  40. data/lib/mongo_mapper/plugins/rails.rb +80 -0
  41. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  42. data/lib/mongo_mapper/plugins/validations.rb +48 -0
  43. data/lib/mongo_mapper/support.rb +213 -0
  44. data/performance/read_write.rb +52 -0
  45. data/specs.watchr +51 -0
  46. data/test/NOTE_ON_TESTING +1 -0
  47. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  48. data/test/functional/associations/test_belongs_to_proxy.rb +93 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +437 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +175 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +216 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +340 -0
  55. data/test/functional/associations/test_one_proxy.rb +149 -0
  56. data/test/functional/test_associations.rb +44 -0
  57. data/test/functional/test_binary.rb +27 -0
  58. data/test/functional/test_callbacks.rb +81 -0
  59. data/test/functional/test_dirty.rb +156 -0
  60. data/test/functional/test_document.rb +1171 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +233 -0
  63. data/test/functional/test_logger.rb +20 -0
  64. data/test/functional/test_modifiers.rb +252 -0
  65. data/test/functional/test_pagination.rb +93 -0
  66. data/test/functional/test_protected.rb +41 -0
  67. data/test/functional/test_string_id_compatibility.rb +67 -0
  68. data/test/functional/test_validations.rb +329 -0
  69. data/test/models.rb +232 -0
  70. data/test/support/custom_matchers.rb +55 -0
  71. data/test/support/timing.rb +16 -0
  72. data/test/test_helper.rb +60 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +103 -0
  75. data/test/unit/serializers/test_json_serializer.rb +189 -0
  76. data/test/unit/test_descendant_appends.rb +71 -0
  77. data/test/unit/test_document.rb +203 -0
  78. data/test/unit/test_dynamic_finder.rb +125 -0
  79. data/test/unit/test_embedded_document.rb +628 -0
  80. data/test/unit/test_finder_options.rb +325 -0
  81. data/test/unit/test_keys.rb +169 -0
  82. data/test/unit/test_mongo_mapper.rb +65 -0
  83. data/test/unit/test_pagination.rb +127 -0
  84. data/test/unit/test_plugins.rb +42 -0
  85. data/test/unit/test_rails.rb +139 -0
  86. data/test/unit/test_rails_compatibility.rb +42 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +350 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +492 -0
  91. metadata +260 -0
@@ -0,0 +1,44 @@
1
+ module MongoMapper
2
+ module DescendantAppends
3
+ def included(model)
4
+ extra_extensions.each { |extension| model.extend(extension) }
5
+ extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
6
+ descendants << model
7
+ end
8
+
9
+ # @api public
10
+ def descendants
11
+ @descendants ||= Set.new
12
+ end
13
+
14
+ # @api public
15
+ def append_extensions(*extensions)
16
+ extra_extensions.concat extensions
17
+
18
+ # Add the extension to existing descendants
19
+ descendants.each do |model|
20
+ extensions.each { |extension| model.extend(extension) }
21
+ end
22
+ end
23
+
24
+ # @api public
25
+ def append_inclusions(*inclusions)
26
+ extra_inclusions.concat inclusions
27
+
28
+ # Add the inclusion to existing descendants
29
+ descendants.each do |model|
30
+ inclusions.each { |inclusion| model.send(:include, inclusion) }
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ def extra_extensions
36
+ @extra_extensions ||= []
37
+ end
38
+
39
+ # @api private
40
+ def extra_inclusions
41
+ @extra_inclusions ||= []
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,402 @@
1
+ module MongoMapper
2
+ module Document
3
+ extend DescendantAppends
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ extend Finders
10
+
11
+ extend Plugins
12
+ plugin Plugins::Associations
13
+ plugin Plugins::Clone
14
+ plugin Plugins::Descendants
15
+ plugin Plugins::Equality
16
+ plugin Plugins::Inspect
17
+ plugin Plugins::Keys
18
+ plugin Plugins::Dirty # for now dirty needs to be after keys
19
+ plugin Plugins::Logger
20
+ plugin Plugins::Pagination
21
+ plugin Plugins::Protected
22
+ plugin Plugins::Rails
23
+ plugin Plugins::Serialization
24
+ plugin Plugins::Validations
25
+ plugin Plugins::Callbacks # for now callbacks needs to be after validations
26
+
27
+ extend Plugins::Validations::DocumentMacros
28
+ end
29
+
30
+ super
31
+ end
32
+
33
+ module ClassMethods
34
+ def inherited(subclass)
35
+ subclass.set_collection_name(collection_name)
36
+ super
37
+ end
38
+
39
+ def ensure_index(name_or_array, options={})
40
+ keys_to_index = if name_or_array.is_a?(Array)
41
+ name_or_array.map { |pair| [pair[0], pair[1]] }
42
+ else
43
+ name_or_array
44
+ end
45
+
46
+ MongoMapper.ensure_index(self, keys_to_index, options)
47
+ end
48
+
49
+ def find!(*args)
50
+ options = args.extract_options!
51
+ case args.first
52
+ when :first then first(options)
53
+ when :last then last(options)
54
+ when :all then find_every(options)
55
+ when Array then find_some(args, options)
56
+ else
57
+ case args.size
58
+ when 0
59
+ raise DocumentNotFound, "Couldn't find without an ID"
60
+ when 1
61
+ find_one!(options.merge({:_id => args[0]}))
62
+ else
63
+ find_some(args, options)
64
+ end
65
+ end
66
+ end
67
+
68
+ def find(*args)
69
+ find!(*args)
70
+ rescue DocumentNotFound
71
+ nil
72
+ end
73
+
74
+ def first(options={})
75
+ find_one(options)
76
+ end
77
+
78
+ def last(options={})
79
+ raise ':order option must be provided when using last' if options[:order].blank?
80
+ find_one(options.merge(:order => invert_order_clause(options[:order])))
81
+ end
82
+
83
+ def all(options={})
84
+ find_every(options)
85
+ end
86
+
87
+ def find_by_id(id)
88
+ find(id)
89
+ end
90
+
91
+ def count(options={})
92
+ collection.find(to_criteria(options)).count
93
+ end
94
+
95
+ def exists?(options={})
96
+ !count(options).zero?
97
+ end
98
+
99
+ def create(*docs)
100
+ initialize_each(*docs) { |doc| doc.save }
101
+ end
102
+
103
+ def create!(*docs)
104
+ initialize_each(*docs) { |doc| doc.save! }
105
+ end
106
+
107
+ def update(*args)
108
+ if args.length == 1
109
+ update_multiple(args[0])
110
+ else
111
+ id, attributes = args
112
+ update_single(id, attributes)
113
+ end
114
+ end
115
+
116
+ def delete(*ids)
117
+ collection.remove(to_criteria(:_id => ids.flatten))
118
+ end
119
+
120
+ def delete_all(options={})
121
+ collection.remove(to_criteria(options))
122
+ end
123
+
124
+ def destroy(*ids)
125
+ find_some(ids.flatten).each(&:destroy)
126
+ end
127
+
128
+ def destroy_all(options={})
129
+ all(options).each(&:destroy)
130
+ end
131
+
132
+ def increment(*args)
133
+ modifier_update('$inc', args)
134
+ end
135
+
136
+ def decrement(*args)
137
+ criteria, keys = criteria_and_keys_from_args(args)
138
+ values, to_decrement = keys.values, {}
139
+ keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
140
+ collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
141
+ end
142
+
143
+ def set(*args)
144
+ modifier_update('$set', args)
145
+ end
146
+
147
+ def push(*args)
148
+ modifier_update('$push', args)
149
+ end
150
+
151
+ def push_all(*args)
152
+ modifier_update('$pushAll', args)
153
+ end
154
+
155
+ def push_uniq(*args)
156
+ criteria, keys = criteria_and_keys_from_args(args)
157
+ keys.each { |key, value | criteria[key] = {'$ne' => value} }
158
+ collection.update(criteria, {'$push' => keys}, :multi => true)
159
+ end
160
+
161
+ def pull(*args)
162
+ modifier_update('$pull', args)
163
+ end
164
+
165
+ def pull_all(*args)
166
+ modifier_update('$pullAll', args)
167
+ end
168
+
169
+ def pop(*args)
170
+ modifier_update('$pop', args)
171
+ end
172
+
173
+ def modifier_update(modifier, args)
174
+ criteria, keys = criteria_and_keys_from_args(args)
175
+ modifiers = {modifier => keys}
176
+ collection.update(criteria, modifiers, :multi => true)
177
+ end
178
+ private :modifier_update
179
+
180
+ def criteria_and_keys_from_args(args)
181
+ keys = args.pop
182
+ criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
183
+ [to_criteria(criteria), keys]
184
+ end
185
+ private :criteria_and_keys_from_args
186
+
187
+ def embeddable?
188
+ false
189
+ end
190
+
191
+ def connection(mongo_connection=nil)
192
+ if mongo_connection.nil?
193
+ @connection ||= MongoMapper.connection
194
+ else
195
+ @connection = mongo_connection
196
+ end
197
+ @connection
198
+ end
199
+
200
+ def set_database_name(name)
201
+ @database_name = name
202
+ end
203
+
204
+ def database_name
205
+ @database_name
206
+ end
207
+
208
+ def database
209
+ if database_name.nil?
210
+ MongoMapper.database
211
+ else
212
+ connection.db(database_name)
213
+ end
214
+ end
215
+
216
+ def set_collection_name(name)
217
+ @collection_name = name
218
+ end
219
+
220
+ def collection_name
221
+ @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
222
+ end
223
+
224
+ def collection
225
+ database.collection(collection_name)
226
+ end
227
+
228
+ def timestamps!
229
+ key :created_at, Time
230
+ key :updated_at, Time
231
+ class_eval { before_save :update_timestamps }
232
+ end
233
+
234
+ def userstamps!
235
+ key :creator_id, ObjectId
236
+ key :updater_id, ObjectId
237
+ belongs_to :creator, :class_name => 'User'
238
+ belongs_to :updater, :class_name => 'User'
239
+ end
240
+
241
+ def single_collection_inherited?
242
+ keys.key?(:_type) && single_collection_inherited_superclass?
243
+ end
244
+
245
+ def single_collection_inherited_superclass?
246
+ superclass.respond_to?(:keys) && superclass.keys.key?(:_type)
247
+ end
248
+
249
+ private
250
+ def initialize_each(*docs)
251
+ instances = []
252
+ docs = [{}] if docs.blank?
253
+ docs.flatten.each do |attrs|
254
+ doc = new(attrs)
255
+ yield(doc)
256
+ instances << doc
257
+ end
258
+ instances.size == 1 ? instances[0] : instances
259
+ end
260
+
261
+ def find_every(options)
262
+ criteria, options = to_finder_options(options)
263
+ collection.find(criteria, options).to_a.map do |doc|
264
+ load(doc)
265
+ end
266
+ end
267
+
268
+ def find_some(ids, options={})
269
+ ids = ids.flatten.compact.uniq
270
+ documents = find_every(options.merge(:_id => ids))
271
+
272
+ if ids.size == documents.size
273
+ documents
274
+ else
275
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
276
+ end
277
+ end
278
+
279
+ def find_one(options={})
280
+ criteria, options = to_finder_options(options)
281
+ if doc = collection.find_one(criteria, options)
282
+ load(doc)
283
+ end
284
+ end
285
+
286
+ def find_one!(options={})
287
+ find_one(options) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
288
+ end
289
+
290
+ def invert_order_clause(order)
291
+ order.split(',').map do |order_segment|
292
+ if order_segment =~ /\sasc/i
293
+ order_segment.sub /\sasc/i, ' desc'
294
+ elsif order_segment =~ /\sdesc/i
295
+ order_segment.sub /\sdesc/i, ' asc'
296
+ else
297
+ "#{order_segment.strip} desc"
298
+ end
299
+ end.join(',')
300
+ end
301
+
302
+ def update_single(id, attrs)
303
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
304
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
305
+ end
306
+
307
+ doc = find(id)
308
+ doc.update_attributes(attrs)
309
+ doc
310
+ end
311
+
312
+ def update_multiple(docs)
313
+ unless docs.is_a?(Hash)
314
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
315
+ end
316
+
317
+ instances = []
318
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
319
+ instances
320
+ end
321
+
322
+ def to_criteria(options={})
323
+ FinderOptions.new(self, options).criteria
324
+ end
325
+
326
+ def to_finder_options(options={})
327
+ FinderOptions.new(self, options).to_a
328
+ end
329
+ end
330
+
331
+ module InstanceMethods
332
+ def collection
333
+ self.class.collection
334
+ end
335
+
336
+ def database
337
+ self.class.database
338
+ end
339
+
340
+ def save(options={})
341
+ options.reverse_merge!(:validate => true)
342
+ perform_validations = options.delete(:validate)
343
+ !perform_validations || valid? ? create_or_update(options) : false
344
+ end
345
+
346
+ def save!
347
+ save || raise(DocumentNotValid.new(self))
348
+ end
349
+
350
+ def update_attributes(attrs={})
351
+ self.attributes = attrs
352
+ save
353
+ end
354
+
355
+ def update_attributes!(attrs={})
356
+ self.attributes = attrs
357
+ save!
358
+ end
359
+
360
+ def destroy
361
+ delete
362
+ end
363
+
364
+ def delete
365
+ self.class.delete(id) unless new?
366
+ end
367
+
368
+ def reload
369
+ doc = self.class.find(_id)
370
+ self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
371
+ self.attributes = doc.attributes
372
+ self
373
+ end
374
+
375
+ private
376
+ def create_or_update(options={})
377
+ result = new? ? create(options) : update(options)
378
+ result != false
379
+ end
380
+
381
+ def create(options={})
382
+ save_to_collection(options)
383
+ end
384
+
385
+ def update(options={})
386
+ save_to_collection(options)
387
+ end
388
+
389
+ def save_to_collection(options={})
390
+ safe = options.delete(:safe) || false
391
+ @new = false
392
+ collection.save(to_mongo, :safe => safe)
393
+ end
394
+
395
+ def update_timestamps
396
+ now = Time.now.utc
397
+ self[:created_at] = now if new? && !created_at?
398
+ self[:updated_at] = now
399
+ end
400
+ end
401
+ end # Document
402
+ end # MongoMapper
@@ -0,0 +1,74 @@
1
+ module MongoMapper
2
+ # @api private
3
+ module Finders
4
+ def dynamic_find(finder, args)
5
+ attributes = {}
6
+ finder.attributes.each_with_index do |attr, index|
7
+ attributes[attr] = args[index]
8
+ end
9
+
10
+ options = args.extract_options!.merge(attributes)
11
+
12
+ if result = find(finder.finder, options)
13
+ result
14
+ else
15
+ if finder.raise?
16
+ raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
17
+ end
18
+
19
+ if finder.instantiator
20
+ self.send(finder.instantiator, attributes)
21
+ end
22
+ end
23
+ end
24
+
25
+ protected
26
+ def method_missing(method, *args, &block)
27
+ finder = DynamicFinder.new(method)
28
+
29
+ if finder.found?
30
+ dynamic_find(finder, args)
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+
37
+ class DynamicFinder
38
+ attr_reader :method, :attributes, :finder, :bang, :instantiator
39
+
40
+ def initialize(method)
41
+ @method = method
42
+ @finder = :first
43
+ @bang = false
44
+ match
45
+ end
46
+
47
+ def found?
48
+ @finder.present?
49
+ end
50
+
51
+ def raise?
52
+ bang == true
53
+ end
54
+
55
+ protected
56
+ def match
57
+ case method.to_s
58
+ when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
59
+ @finder = :all if $1 == 'all_by'
60
+ names = $2
61
+ when /^find_by_([_a-zA-Z]\w*)\!$/
62
+ @bang = true
63
+ names = $1
64
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
65
+ @instantiator = $1 == 'initialize' ? :new : :create
66
+ names = $2
67
+ else
68
+ @finder = nil
69
+ end
70
+
71
+ @attributes = names && names.split('_and_')
72
+ end
73
+ end
74
+ end