mongo_mapper 0.5.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 (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