mongo_mapper-unstable 2009.10.11

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