jmonteiro-mongo_mapper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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