mongo_mapper-unstable 2009.10.11

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