mongo_mapper 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +7 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +39 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper.rb +92 -0
  8. data/lib/mongo_mapper/associations.rb +86 -0
  9. data/lib/mongo_mapper/associations/base.rb +83 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  12. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  13. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  14. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  16. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongo_mapper/associations/proxy.rb +64 -0
  19. data/lib/mongo_mapper/callbacks.rb +106 -0
  20. data/lib/mongo_mapper/document.rb +317 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  22. data/lib/mongo_mapper/embedded_document.rb +354 -0
  23. data/lib/mongo_mapper/finder_options.rb +94 -0
  24. data/lib/mongo_mapper/key.rb +32 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +51 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/save_with_validation.rb +19 -0
  30. data/lib/mongo_mapper/serialization.rb +55 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +157 -0
  33. data/lib/mongo_mapper/validations.rb +69 -0
  34. data/mongo_mapper.gemspec +156 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/custom_matchers.rb +48 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +54 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +46 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  40. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  41. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  42. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  43. data/test/functional/associations/test_many_proxy.rb +331 -0
  44. data/test/functional/test_associations.rb +48 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_document.rb +951 -0
  48. data/test/functional/test_embedded_document.rb +97 -0
  49. data/test/functional/test_pagination.rb +87 -0
  50. data/test/functional/test_rails_compatibility.rb +30 -0
  51. data/test/functional/test_validations.rb +279 -0
  52. data/test/models.rb +169 -0
  53. data/test/test_helper.rb +29 -0
  54. data/test/unit/serializers/test_json_serializer.rb +189 -0
  55. data/test/unit/test_association_base.rb +144 -0
  56. data/test/unit/test_document.rb +165 -0
  57. data/test/unit/test_dynamic_finder.rb +125 -0
  58. data/test/unit/test_embedded_document.rb +645 -0
  59. data/test/unit/test_finder_options.rb +193 -0
  60. data/test/unit/test_key.rb +163 -0
  61. data/test/unit/test_mongomapper.rb +28 -0
  62. data/test/unit/test_observing.rb +101 -0
  63. data/test/unit/test_pagination.rb +109 -0
  64. data/test/unit/test_rails_compatibility.rb +39 -0
  65. data/test/unit/test_serializations.rb +52 -0
  66. data/test/unit/test_support.rb +272 -0
  67. data/test/unit/test_time_zones.rb +40 -0
  68. data/test/unit/test_validations.rb +503 -0
  69. metadata +204 -0
@@ -0,0 +1,317 @@
1
+ require 'set'
2
+
3
+ module MongoMapper
4
+ module Document
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include EmbeddedDocument
8
+ include InstanceMethods
9
+ include Observing
10
+ include Callbacks
11
+ include SaveWithValidation
12
+ include RailsCompatibility::Document
13
+ extend Validations::Macros
14
+ extend ClassMethods
15
+ extend Finders
16
+
17
+ def self.per_page
18
+ 25
19
+ end unless respond_to?(:per_page)
20
+ end
21
+
22
+ descendants << model
23
+ end
24
+
25
+ def self.descendants
26
+ @descendants ||= Set.new
27
+ end
28
+
29
+ module ClassMethods
30
+ def find(*args)
31
+ options = args.extract_options!
32
+
33
+ case args.first
34
+ when :first then find_first(options)
35
+ when :last then find_last(options)
36
+ when :all then find_every(options)
37
+ else find_from_ids(args, options)
38
+ end
39
+ end
40
+
41
+ def paginate(options)
42
+ per_page = options.delete(:per_page) || self.per_page
43
+ page = options.delete(:page)
44
+ total_entries = count(options[:conditions] || {})
45
+ collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
46
+
47
+ options[:limit] = collection.limit
48
+ options[:skip] = collection.skip
49
+
50
+ collection.subject = find_every(options)
51
+ collection
52
+ end
53
+
54
+ def first(options={})
55
+ find_first(options)
56
+ end
57
+
58
+ def last(options={})
59
+ find_last(options)
60
+ end
61
+
62
+ def all(options={})
63
+ find_every(options)
64
+ end
65
+
66
+ def find_by_id(id)
67
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
68
+ if doc = collection.find_one(criteria)
69
+ new(doc)
70
+ end
71
+ end
72
+
73
+ def count(conditions={})
74
+ collection.find(FinderOptions.to_mongo_criteria(conditions)).count
75
+ end
76
+
77
+ def create(*docs)
78
+ instances = []
79
+ docs = [{}] if docs.blank?
80
+ docs.flatten.each do |attrs|
81
+ doc = new(attrs); doc.save
82
+ instances << doc
83
+ end
84
+ instances.size == 1 ? instances[0] : instances
85
+ end
86
+
87
+ # For updating single document
88
+ # Person.update(1, {:foo => 'bar'})
89
+ #
90
+ # For updating multiple documents at once:
91
+ # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
92
+ def update(*args)
93
+ updating_multiple = args.length == 1
94
+ if updating_multiple
95
+ update_multiple(args[0])
96
+ else
97
+ id, attributes = args
98
+ update_single(id, attributes)
99
+ end
100
+ end
101
+
102
+ def delete(*ids)
103
+ criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
104
+ collection.remove(criteria)
105
+ end
106
+
107
+ def delete_all(conditions={})
108
+ criteria = FinderOptions.to_mongo_criteria(conditions)
109
+ collection.remove(criteria)
110
+ end
111
+
112
+ def destroy(*ids)
113
+ find_some(ids.flatten).each(&:destroy)
114
+ end
115
+
116
+ def destroy_all(conditions={})
117
+ find(:all, :conditions => conditions).each(&:destroy)
118
+ end
119
+
120
+ def connection(mongo_connection=nil)
121
+ if mongo_connection.nil?
122
+ @connection ||= MongoMapper.connection
123
+ else
124
+ @connection = mongo_connection
125
+ end
126
+ @connection
127
+ end
128
+
129
+ def database(name=nil)
130
+ if name.nil?
131
+ @database ||= MongoMapper.database
132
+ else
133
+ @database = connection.db(name)
134
+ end
135
+ @database
136
+ end
137
+
138
+ # Changes the collection name from the default to whatever you want
139
+ def set_collection_name(name=nil)
140
+ @collection = nil
141
+ @collection_name = name
142
+ end
143
+
144
+ # Returns the collection name, if not set, defaults to class name tableized
145
+ def collection_name
146
+ @collection_name ||= self.to_s.demodulize.tableize
147
+ end
148
+
149
+ # Returns the mongo ruby driver collection object
150
+ def collection
151
+ @collection ||= database.collection(collection_name)
152
+ end
153
+
154
+ def timestamps!
155
+ key :created_at, Time
156
+ key :updated_at, Time
157
+
158
+ class_eval { before_save :update_timestamps }
159
+ end
160
+
161
+ protected
162
+ def method_missing(method, *args)
163
+ finder = DynamicFinder.new(method)
164
+
165
+ if finder.found?
166
+ meta_def(finder.method) { |*args| dynamic_find(finder, args) }
167
+ send(finder.method, *args)
168
+ else
169
+ super
170
+ end
171
+ end
172
+
173
+ private
174
+ def find_every(options)
175
+ criteria, options = FinderOptions.new(options).to_a
176
+ collection.find(criteria, options).to_a.map { |doc| new(doc) }
177
+ end
178
+
179
+ def find_first(options)
180
+ options.merge!(:limit => 1)
181
+ options[:order] ||= '$natural'
182
+ find_every(options)[0]
183
+ end
184
+
185
+ def find_last(options)
186
+ options.merge!(:limit => 1)
187
+ options[:order] = invert_order_clause(options)
188
+ find_every(options)[0]
189
+ end
190
+
191
+ def invert_order_clause(options)
192
+ return '$natural desc' unless options[:order]
193
+ options[:order].split(',').map do |order_segment|
194
+ if order_segment =~ /\sasc/i
195
+ order_segment.sub /\sasc/i, ' desc'
196
+ elsif order_segment =~ /\sdesc/i
197
+ order_segment.sub /\sdesc/i, ' asc'
198
+ else
199
+ "#{order_segment.strip} desc"
200
+ end
201
+ end.join(',')
202
+ end
203
+
204
+ def find_some(ids, options={})
205
+ documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
206
+ if ids.size == documents.size
207
+ documents
208
+ else
209
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
210
+ end
211
+ end
212
+
213
+ def find_one(id, options={})
214
+ if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
215
+ doc
216
+ else
217
+ raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
218
+ end
219
+ end
220
+
221
+ def find_from_ids(ids, options={})
222
+ ids = ids.flatten.compact.uniq
223
+
224
+ case ids.size
225
+ when 0
226
+ raise(DocumentNotFound, "Couldn't find without an ID")
227
+ when 1
228
+ find_one(ids[0], options)
229
+ else
230
+ find_some(ids, options)
231
+ end
232
+ end
233
+
234
+ def update_single(id, attrs)
235
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
236
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
237
+ end
238
+
239
+ doc = find(id)
240
+ doc.update_attributes(attrs)
241
+ doc
242
+ end
243
+
244
+ def update_multiple(docs)
245
+ unless docs.is_a?(Hash)
246
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
247
+ end
248
+
249
+ instances = []
250
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
251
+ instances
252
+ end
253
+ end
254
+
255
+ module InstanceMethods
256
+ def collection
257
+ self.class.collection
258
+ end
259
+
260
+ def new?
261
+ read_attribute('_id').blank? || using_custom_id?
262
+ end
263
+
264
+ def save
265
+ create_or_update
266
+ end
267
+
268
+ def save!
269
+ create_or_update || raise(DocumentNotValid.new(self))
270
+ end
271
+
272
+ def destroy
273
+ return false if frozen?
274
+
275
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
276
+ collection.remove(criteria) unless new?
277
+ freeze
278
+ end
279
+
280
+ private
281
+ def create_or_update
282
+ result = new? ? create : update
283
+ result != false
284
+ end
285
+
286
+ def create
287
+ assign_id
288
+ save_to_collection
289
+ end
290
+
291
+ def assign_id
292
+ if read_attribute(:_id).blank?
293
+ write_attribute(:_id, Mongo::ObjectID.new.to_s)
294
+ end
295
+ end
296
+
297
+ def update
298
+ save_to_collection
299
+ end
300
+
301
+ def save_to_collection
302
+ clear_custom_id_flag
303
+ collection.save(to_mongo)
304
+ end
305
+
306
+ def update_timestamps
307
+ now = Time.now.utc
308
+ write_attribute('created_at', now) if new?
309
+ write_attribute('updated_at', now)
310
+ end
311
+
312
+ def clear_custom_id_flag
313
+ @using_custom_id = nil
314
+ end
315
+ end
316
+ end # Document
317
+ end # MongoMapper
@@ -0,0 +1,35 @@
1
+ module MongoMapper
2
+ class DynamicFinder
3
+ attr_reader :method, :attributes, :finder, :bang, :instantiator
4
+
5
+ def initialize(method)
6
+ @method = method
7
+ @finder = :first
8
+ @bang = false
9
+ match()
10
+ end
11
+
12
+ def found?
13
+ @finder.present?
14
+ end
15
+
16
+ protected
17
+ def match
18
+ case method.to_s
19
+ when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
20
+ @finder = :all if $1 == 'all_by'
21
+ names = $2
22
+ when /^find_by_([_a-zA-Z]\w*)\!$/
23
+ @bang = true
24
+ names = $1
25
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
26
+ @instantiator = $1 == 'initialize' ? :new : :create
27
+ names = $2
28
+ else
29
+ @finder = nil
30
+ end
31
+
32
+ @attributes = names && names.split('_and_')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,354 @@
1
+ require 'observer'
2
+
3
+ module MongoMapper
4
+ module EmbeddedDocument
5
+ def self.included(model)
6
+ model.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+
10
+ extend Associations::ClassMethods
11
+ include Associations::InstanceMethods
12
+
13
+ include RailsCompatibility::EmbeddedDocument
14
+ include Validatable
15
+ include Serialization
16
+
17
+ extend Validations::Macros
18
+
19
+ key :_id, String
20
+ attr_accessor :_root_document
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ def inherited(subclass)
26
+ unless subclass.embeddable?
27
+ subclass.set_collection_name(collection_name)
28
+ end
29
+
30
+ (@subclasses ||= []) << subclass
31
+ end
32
+
33
+ def subclasses
34
+ @subclasses
35
+ end
36
+
37
+ def keys
38
+ @keys ||= if parent = parent_model
39
+ parent.keys.dup
40
+ else
41
+ HashWithIndifferentAccess.new
42
+ end
43
+ end
44
+
45
+ def key(*args)
46
+ key = Key.new(*args)
47
+
48
+ if keys[key.name].blank?
49
+ keys[key.name] = key
50
+
51
+ create_accessors_for(key)
52
+ add_to_subclasses(*args)
53
+ apply_validations_for(key)
54
+ create_indexes_for(key)
55
+
56
+ key
57
+ end
58
+ end
59
+
60
+ def add_to_subclasses(*args)
61
+ return if subclasses.blank?
62
+
63
+ subclasses.each do |subclass|
64
+ subclass.key(*args)
65
+ end
66
+ end
67
+
68
+ def ensure_index(name_or_array, options={})
69
+ keys_to_index = if name_or_array.is_a?(Array)
70
+ name_or_array.map { |pair| [pair[0], pair[1]] }
71
+ else
72
+ name_or_array
73
+ end
74
+
75
+ collection.create_index(keys_to_index, options.delete(:unique))
76
+ end
77
+
78
+ def embeddable?
79
+ !self.ancestors.include?(Document)
80
+ end
81
+
82
+ def parent_model
83
+ (ancestors - [self,EmbeddedDocument]).find do |parent_class|
84
+ parent_class.ancestors.include?(EmbeddedDocument)
85
+ end
86
+ end
87
+
88
+ def to_mongo(instance)
89
+ return nil if instance.nil?
90
+ instance.to_mongo
91
+ end
92
+
93
+ def from_mongo(instance_or_hash)
94
+ return nil if instance_or_hash.nil?
95
+
96
+ if instance_or_hash.is_a?(self)
97
+ instance_or_hash
98
+ else
99
+ new(instance_or_hash)
100
+ end
101
+ end
102
+
103
+ private
104
+ def accessors_module
105
+ if const_defined?('MongoMapperKeys')
106
+ const_get 'MongoMapperKeys'
107
+ else
108
+ const_set 'MongoMapperKeys', Module.new
109
+ end
110
+ end
111
+
112
+ def create_accessors_for(key)
113
+ accessors_module.module_eval <<-end_eval
114
+ def #{key.name}
115
+ read_attribute(:'#{key.name}')
116
+ end
117
+
118
+ def #{key.name}_before_typecast
119
+ read_attribute_before_typecast(:'#{key.name}')
120
+ end
121
+
122
+ def #{key.name}=(value)
123
+ write_attribute(:'#{key.name}', value)
124
+ end
125
+
126
+ def #{key.name}?
127
+ read_attribute(:#{key.name}).present?
128
+ end
129
+ end_eval
130
+ include accessors_module
131
+ end
132
+
133
+ def create_indexes_for(key)
134
+ ensure_index key.name if key.options[:index]
135
+ end
136
+
137
+ def apply_validations_for(key)
138
+ attribute = key.name.to_sym
139
+
140
+ if key.options[:required]
141
+ validates_presence_of(attribute)
142
+ end
143
+
144
+ if key.options[:unique]
145
+ validates_uniqueness_of(attribute)
146
+ end
147
+
148
+ if key.options[:numeric]
149
+ number_options = key.type == Integer ? {:only_integer => true} : {}
150
+ validates_numericality_of(attribute, number_options)
151
+ end
152
+
153
+ if key.options[:format]
154
+ validates_format_of(attribute, :with => key.options[:format])
155
+ end
156
+
157
+ if key.options[:length]
158
+ length_options = case key.options[:length]
159
+ when Integer
160
+ {:minimum => 0, :maximum => key.options[:length]}
161
+ when Range
162
+ {:within => key.options[:length]}
163
+ when Hash
164
+ key.options[:length]
165
+ end
166
+ validates_length_of(attribute, length_options)
167
+ end
168
+ end
169
+ end
170
+
171
+ module InstanceMethods
172
+ def initialize(attrs={})
173
+ unless attrs.nil?
174
+ self.class.associations.each_pair do |name, association|
175
+ if collection = attrs.delete(name)
176
+ if association.many? && association.klass.embeddable?
177
+ root_document = attrs[:_root_document] || self
178
+ collection.each do |doc|
179
+ doc[:_root_document] = root_document
180
+ end
181
+ end
182
+ send("#{association.name}=", collection)
183
+ end
184
+ end
185
+
186
+ self.attributes = attrs
187
+
188
+ if respond_to?(:_type=) && self['_type'].blank?
189
+ self._type = self.class.name
190
+ end
191
+ end
192
+
193
+ if self.class.embeddable?
194
+ if read_attribute(:_id).blank?
195
+ write_attribute :_id, Mongo::ObjectID.new.to_s
196
+ @new_document = true
197
+ else
198
+ @new_document = false
199
+ end
200
+ end
201
+ end
202
+
203
+ def new?
204
+ !!@new_document
205
+ end
206
+
207
+ def attributes=(attrs)
208
+ return if attrs.blank?
209
+ attrs.each_pair do |name, value|
210
+ writer_method = "#{name}="
211
+
212
+ if respond_to?(writer_method)
213
+ self.send(writer_method, value)
214
+ else
215
+ self[name.to_s] = value
216
+ end
217
+ end
218
+ end
219
+
220
+ def attributes
221
+ attrs = HashWithIndifferentAccess.new
222
+
223
+ embedded_keys.each do |key|
224
+ puts key.inspect
225
+ attrs[key.name] = read_attribute(key.name).try(:attributes)
226
+ end
227
+
228
+ non_embedded_keys.each do |key|
229
+ attrs[key.name] = read_attribute(key.name)
230
+ end
231
+
232
+ embedded_associations.each do |association|
233
+ documents = instance_variable_get(association.ivar)
234
+ next if documents.nil?
235
+ attrs[association.name] = documents.collect { |doc| doc.attributes }
236
+ end
237
+
238
+ attrs
239
+ end
240
+
241
+ def to_mongo
242
+ attrs = HashWithIndifferentAccess.new
243
+
244
+ _keys.each_pair do |name, key|
245
+ value = key.set(read_attribute(key.name))
246
+ attrs[name] = value unless value.nil?
247
+ end
248
+
249
+ embedded_associations.each do |association|
250
+ if documents = instance_variable_get(association.ivar)
251
+ attrs[association.name] = documents.map { |document| document.to_mongo }
252
+ end
253
+ end
254
+
255
+ attrs
256
+ end
257
+
258
+ def clone
259
+ clone_attributes = self.attributes
260
+ clone_attributes.delete("_id")
261
+ self.class.new(clone_attributes)
262
+ end
263
+
264
+ def [](name)
265
+ read_attribute(name)
266
+ end
267
+
268
+ def []=(name, value)
269
+ ensure_key_exists(name)
270
+ write_attribute(name, value)
271
+ end
272
+
273
+ def ==(other)
274
+ other.is_a?(self.class) && id == other.id
275
+ end
276
+
277
+ def id
278
+ read_attribute(:_id)
279
+ end
280
+
281
+ def id=(value)
282
+ @using_custom_id = true
283
+ write_attribute :_id, value
284
+ end
285
+
286
+ def using_custom_id?
287
+ !!@using_custom_id
288
+ end
289
+
290
+ def inspect
291
+ attributes_as_nice_string = key_names.collect do |name|
292
+ "#{name}: #{read_attribute(name).inspect}"
293
+ end.join(", ")
294
+ "#<#{self.class} #{attributes_as_nice_string}>"
295
+ end
296
+
297
+ def save
298
+ if _root_document
299
+ _root_document.save
300
+ end
301
+ end
302
+
303
+ def update_attributes(attrs={})
304
+ self.attributes = attrs
305
+ save
306
+ end
307
+
308
+ private
309
+ def _keys
310
+ self.class.keys
311
+ end
312
+
313
+ def key_names
314
+ _keys.keys
315
+ end
316
+
317
+ def non_embedded_keys
318
+ _keys.values.select { |key| !key.embeddable? }
319
+ end
320
+
321
+ def embedded_keys
322
+ _keys.values.select { |key| key.embeddable? }
323
+ end
324
+
325
+ def ensure_key_exists(name)
326
+ self.class.key(name) unless respond_to?("#{name}=")
327
+ end
328
+
329
+ def read_attribute(name)
330
+ value = _keys[name].get(instance_variable_get("@#{name}"))
331
+ instance_variable_set "@#{name}", value if !frozen?
332
+ value
333
+ end
334
+
335
+ def read_attribute_before_typecast(name)
336
+ instance_variable_get("@#{name}_before_typecast")
337
+ end
338
+
339
+ def write_attribute(name, value)
340
+ key = _keys[name]
341
+ instance_variable_set "@#{name}_before_typecast", value
342
+ instance_variable_set "@#{name}", key.set(value)
343
+ end
344
+
345
+ def embedded_associations
346
+ self.class.associations.select do |name, association|
347
+ association.embeddable?
348
+ end.map do |name, association|
349
+ association
350
+ end
351
+ end
352
+ end # InstanceMethods
353
+ end # EmbeddedDocument
354
+ end # MongoMapper