mongo_mapper-rails3 0.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +60 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +60 -0
  8. data/lib/mongo_mapper.rb +131 -0
  9. data/lib/mongo_mapper/document.rb +439 -0
  10. data/lib/mongo_mapper/embedded_document.rb +68 -0
  11. data/lib/mongo_mapper/plugins.rb +30 -0
  12. data/lib/mongo_mapper/plugins/associations.rb +106 -0
  13. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  14. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  15. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  16. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  17. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  18. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +141 -0
  19. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  20. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  21. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  22. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  23. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  24. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  25. data/lib/mongo_mapper/plugins/associations/proxy.rb +119 -0
  26. data/lib/mongo_mapper/plugins/callbacks.rb +87 -0
  27. data/lib/mongo_mapper/plugins/clone.rb +14 -0
  28. data/lib/mongo_mapper/plugins/descendants.rb +17 -0
  29. data/lib/mongo_mapper/plugins/dirty.rb +120 -0
  30. data/lib/mongo_mapper/plugins/equality.rb +24 -0
  31. data/lib/mongo_mapper/plugins/identity_map.rb +124 -0
  32. data/lib/mongo_mapper/plugins/inspect.rb +15 -0
  33. data/lib/mongo_mapper/plugins/keys.rb +310 -0
  34. data/lib/mongo_mapper/plugins/logger.rb +19 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +26 -0
  36. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  37. data/lib/mongo_mapper/plugins/protected.rb +46 -0
  38. data/lib/mongo_mapper/plugins/rails.rb +46 -0
  39. data/lib/mongo_mapper/plugins/serialization.rb +50 -0
  40. data/lib/mongo_mapper/plugins/validations.rb +88 -0
  41. data/lib/mongo_mapper/query.rb +130 -0
  42. data/lib/mongo_mapper/support.rb +217 -0
  43. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  44. data/lib/mongo_mapper/support/find.rb +77 -0
  45. data/mongo_mapper-rails3.gemspec +208 -0
  46. data/performance/read_write.rb +52 -0
  47. data/specs.watchr +51 -0
  48. data/test/NOTE_ON_TESTING +1 -0
  49. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  50. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  51. data/test/functional/associations/test_in_array_proxy.rb +321 -0
  52. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  53. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  54. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  55. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  56. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  57. data/test/functional/associations/test_one_proxy.rb +161 -0
  58. data/test/functional/test_associations.rb +44 -0
  59. data/test/functional/test_binary.rb +27 -0
  60. data/test/functional/test_callbacks.rb +81 -0
  61. data/test/functional/test_dirty.rb +163 -0
  62. data/test/functional/test_document.rb +1244 -0
  63. data/test/functional/test_embedded_document.rb +125 -0
  64. data/test/functional/test_identity_map.rb +508 -0
  65. data/test/functional/test_logger.rb +20 -0
  66. data/test/functional/test_modifiers.rb +252 -0
  67. data/test/functional/test_pagination.rb +93 -0
  68. data/test/functional/test_protected.rb +161 -0
  69. data/test/functional/test_string_id_compatibility.rb +67 -0
  70. data/test/functional/test_validations.rb +329 -0
  71. data/test/models.rb +232 -0
  72. data/test/support/custom_matchers.rb +55 -0
  73. data/test/support/timing.rb +16 -0
  74. data/test/test_helper.rb +59 -0
  75. data/test/unit/associations/test_base.rb +207 -0
  76. data/test/unit/associations/test_proxy.rb +105 -0
  77. data/test/unit/serializers/test_json_serializer.rb +189 -0
  78. data/test/unit/test_descendant_appends.rb +71 -0
  79. data/test/unit/test_document.rb +231 -0
  80. data/test/unit/test_dynamic_finder.rb +123 -0
  81. data/test/unit/test_embedded_document.rb +663 -0
  82. data/test/unit/test_keys.rb +169 -0
  83. data/test/unit/test_lint.rb +8 -0
  84. data/test/unit/test_mongo_mapper.rb +125 -0
  85. data/test/unit/test_pagination.rb +160 -0
  86. data/test/unit/test_plugins.rb +51 -0
  87. data/test/unit/test_query.rb +334 -0
  88. data/test/unit/test_rails.rb +123 -0
  89. data/test/unit/test_rails_compatibility.rb +57 -0
  90. data/test/unit/test_serialization.rb +51 -0
  91. data/test/unit/test_support.rb +362 -0
  92. data/test/unit/test_time_zones.rb +39 -0
  93. data/test/unit/test_validations.rb +557 -0
  94. metadata +344 -0
@@ -0,0 +1,87 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ extend ActiveModel::Callbacks
7
+
8
+ # Define all the callbacks that are accepted by the document.
9
+ define_model_callbacks \
10
+ :create,
11
+ :destroy,
12
+ :save,
13
+ :update,
14
+ :terminator => false
15
+
16
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
17
+
18
+ extend ValidationCallbacks
19
+
20
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
21
+ alias_method_chain method, :callbacks
22
+ end
23
+ end
24
+
25
+ module ValidationCallbacks
26
+ def before_validation(*args, &block)
27
+ options = args.extract_options!
28
+ options[:if] = Array(options[:if])
29
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
30
+ set_callback(:validation, :before, *(args << options), &block)
31
+ end
32
+
33
+ def after_validation(*args, &block)
34
+ options = args.extract_options!
35
+ options[:prepend] = true
36
+ options[:if] = Array(options[:if])
37
+ options[:if] << "!halted && value != false"
38
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
39
+ set_callback(:validation, :after, *(args << options), &block)
40
+ end
41
+
42
+ def before_validation_on_what(what, *args, &block)
43
+ options = args.extract_options!
44
+ options[:on] = what
45
+ before_validation(*(args << options), &block)
46
+ end
47
+
48
+ def before_validation_on_create(*args, &block); before_validation_on_what(:create, *args, &block); end
49
+ def before_validation_on_update(*args, &block); before_validation_on_what(:update, *args, &block); end
50
+ end
51
+
52
+ def create_or_update_with_callbacks(*args) #:nodoc:
53
+ _run_save_callbacks do
54
+ create_or_update_without_callbacks(*args)
55
+ end
56
+ end
57
+ private :create_or_update_with_callbacks
58
+
59
+ def create_with_callbacks(*args) #:nodoc:
60
+ _run_create_callbacks do
61
+ create_without_callbacks(*args)
62
+ end
63
+ end
64
+ private :create_with_callbacks
65
+
66
+ def update_with_callbacks(*args) #:nodoc:
67
+ _run_update_callbacks do
68
+ update_without_callbacks(*args)
69
+ end
70
+ end
71
+ private :update_with_callbacks
72
+
73
+ def valid_with_callbacks? #:nodoc:
74
+ @_on_validate = new_record? ? :create : :update
75
+ _run_validation_callbacks do
76
+ valid_without_callbacks?
77
+ end
78
+ end
79
+
80
+ def destroy_with_callbacks #:nodoc:
81
+ _run_destroy_callbacks do
82
+ destroy_without_callbacks
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,14 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Clone
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods
6
+ def clone
7
+ clone_attributes = self.attributes
8
+ clone_attributes.delete("_id")
9
+ self.class.new(clone_attributes)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Descendants
4
+ extend ActiveSupport::Concern
5
+ module ClassMethods
6
+ def inherited(descendant)
7
+ (@descendants ||= []) << descendant
8
+ super
9
+ end
10
+
11
+ def descendants
12
+ @descendants
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Dirty
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods
6
+ def method_missing(method, *args, &block)
7
+ if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
8
+ method_suffix = $1
9
+ key = method.to_s.gsub(method_suffix, '')
10
+
11
+ if key_names.include?(key)
12
+ case method_suffix
13
+ when '_changed?'
14
+ key_changed?(key)
15
+ when '_change'
16
+ key_change(key)
17
+ when '_will_change!'
18
+ key_will_change!(key)
19
+ when '_was'
20
+ key_was(key)
21
+ end
22
+ else
23
+ super
24
+ end
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def changed?
31
+ !changed_keys.empty?
32
+ end
33
+
34
+ def changed
35
+ changed_keys.keys
36
+ end
37
+
38
+ def changes
39
+ changed.inject({}) { |h, key| h[key] = key_change(key); h }
40
+ end
41
+
42
+ def initialize(*args)
43
+ super
44
+ changed_keys.clear if args.first.blank? || !new?
45
+ end
46
+
47
+ def save(*args)
48
+ if status = super
49
+ changed_keys.clear
50
+ end
51
+ status
52
+ end
53
+
54
+ def save!(*args)
55
+ status = super
56
+ changed_keys.clear
57
+ status
58
+ end
59
+
60
+ def reload(*args)
61
+ document = super
62
+ changed_keys.clear
63
+ document
64
+ end
65
+
66
+ private
67
+ def clone_key_value(key)
68
+ value = read_key(key)
69
+ value.duplicable? ? value.clone : value
70
+ rescue TypeError, NoMethodError
71
+ value
72
+ end
73
+
74
+ def changed_keys
75
+ @changed_keys ||= {}
76
+ end
77
+
78
+ def key_changed?(key)
79
+ changed_keys.include?(key)
80
+ end
81
+
82
+ def key_change(key)
83
+ [changed_keys[key], __send__(key)] if key_changed?(key)
84
+ end
85
+
86
+ def key_was(key)
87
+ key_changed?(key) ? changed_keys[key] : __send__(key)
88
+ end
89
+
90
+ def key_will_change!(key)
91
+ changed_keys[key] = clone_key_value(key)
92
+ end
93
+
94
+ def write_key(key, value)
95
+ key = key.to_s
96
+
97
+ if changed_keys.include?(key)
98
+ old = changed_keys[key]
99
+ changed_keys.delete(key) unless value_changed?(key, old, value)
100
+ else
101
+ old = clone_key_value(key)
102
+ changed_keys[key] = old if value_changed?(key, old, value)
103
+ end
104
+
105
+ super(key, value)
106
+ end
107
+
108
+ def value_changed?(key_name, old, value)
109
+ key = keys[key_name]
110
+
111
+ if key.number? && value.blank?
112
+ value = nil
113
+ end
114
+
115
+ old != value
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,24 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Equality
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods
6
+ def ==(other)
7
+ other.is_a?(self.class) && _id == other._id
8
+ end
9
+
10
+ def eql?(other)
11
+ self == other
12
+ end
13
+
14
+ def equal?(other)
15
+ object_id === other.object_id
16
+ end
17
+
18
+ def hash
19
+ _id.hash
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,124 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module IdentityMap
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.models
7
+ @models ||= Set.new
8
+ end
9
+
10
+ def self.clear
11
+ models.each { |m| m.identity_map.clear }
12
+ end
13
+
14
+ def self.configure(model)
15
+ IdentityMap.models << model
16
+ end
17
+
18
+ module ClassMethods
19
+ def inherited(descendant)
20
+ descendant.identity_map = identity_map
21
+ super
22
+ end
23
+
24
+ def identity_map
25
+ @identity_map ||= {}
26
+ end
27
+
28
+ def identity_map=(v)
29
+ @identity_map = v
30
+ end
31
+
32
+ def find_one(options={})
33
+ criteria, query_options = to_query(options)
34
+
35
+ if simple_find?(criteria) && identity_map.key?(criteria[:_id])
36
+ identity_map[criteria[:_id]]
37
+ else
38
+ super.tap do |document|
39
+ remove_documents_from_map(document) if selecting_fields?(query_options)
40
+ end
41
+ end
42
+ end
43
+
44
+ def find_many(options)
45
+ criteria, query_options = to_query(options)
46
+ super.tap do |documents|
47
+ remove_documents_from_map(documents) if selecting_fields?(query_options)
48
+ end
49
+ end
50
+
51
+ def load(attrs)
52
+ document = identity_map[attrs['_id']]
53
+
54
+ if document.nil? || identity_map_off?
55
+ document = super
56
+ identity_map[document._id] = document if identity_map_on?
57
+ end
58
+
59
+ document
60
+ end
61
+
62
+ def identity_map_status
63
+ defined?(@identity_map_status) ? @identity_map_status : true
64
+ end
65
+
66
+ def identity_map_on
67
+ @identity_map_status = true
68
+ end
69
+
70
+ def identity_map_off
71
+ @identity_map_status = false
72
+ end
73
+
74
+ def identity_map_on?
75
+ identity_map_status
76
+ end
77
+
78
+ def identity_map_off?
79
+ !identity_map_on?
80
+ end
81
+
82
+ def without_identity_map(&block)
83
+ identity_map_off
84
+ yield
85
+ ensure
86
+ identity_map_on
87
+ end
88
+
89
+ private
90
+ def remove_documents_from_map(*documents)
91
+ documents.flatten.compact.each do |document|
92
+ identity_map.delete(document._id)
93
+ end
94
+ end
95
+
96
+ def simple_find?(criteria)
97
+ criteria.keys == [:_id] || criteria.keys.to_set == [:_id, :_type].to_set
98
+ end
99
+
100
+ def selecting_fields?(options)
101
+ !options[:fields].nil?
102
+ end
103
+ end
104
+
105
+ module InstanceMethods
106
+ def identity_map
107
+ self.class.identity_map
108
+ end
109
+
110
+ def save(*args)
111
+ if result = super
112
+ identity_map[_id] = self if self.class.identity_map_on?
113
+ end
114
+ result
115
+ end
116
+
117
+ def delete
118
+ identity_map.delete(_id) if self.class.identity_map_on?
119
+ super
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,15 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Inspect
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods
6
+ def inspect
7
+ attributes_as_nice_string = key_names.collect do |name|
8
+ "#{name}: #{self[name].inspect}"
9
+ end.join(", ")
10
+ "#<#{self.class} #{attributes_as_nice_string}>"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,310 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Keys
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.configure(model)
7
+ model.key :_id, ObjectId
8
+ end
9
+
10
+ module ClassMethods
11
+ def inherited(descendant)
12
+ descendant.instance_variable_set(:@keys, keys.dup)
13
+ super
14
+ end
15
+
16
+ def keys
17
+ @keys ||= HashWithIndifferentAccess.new
18
+ end
19
+
20
+ def key(*args)
21
+ key = Key.new(*args)
22
+ keys[key.name] = key
23
+
24
+ create_accessors_for(key)
25
+ create_key_in_descendants(*args)
26
+ create_indexes_for(key)
27
+ create_validations_for(key)
28
+
29
+ key
30
+ end
31
+
32
+ def using_object_id?
33
+ object_id_key?(:_id)
34
+ end
35
+
36
+ def object_id_key?(name)
37
+ key = keys[name.to_s]
38
+ key && key.type == ObjectId
39
+ end
40
+
41
+ def to_mongo(instance)
42
+ return nil if instance.nil?
43
+ instance.to_mongo
44
+ end
45
+
46
+ def from_mongo(value)
47
+ return nil if value.nil?
48
+ value.is_a?(self) ? value : load(value)
49
+ end
50
+
51
+ # load is overridden in identity map to ensure same objects are loaded
52
+ def load(attrs)
53
+ begin
54
+ klass = attrs['_type'].present? ? attrs['_type'].constantize : self
55
+ klass.new(attrs, true)
56
+ rescue NameError
57
+ new(attrs, true)
58
+ end
59
+ end
60
+
61
+ private
62
+ def accessors_module
63
+ module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
64
+ const_defined?('MongoMapperKeys')
65
+ else
66
+ const_defined?('MongoMapperKeys', false)
67
+ end
68
+
69
+ if module_defined
70
+ const_get 'MongoMapperKeys'
71
+ else
72
+ const_set 'MongoMapperKeys', Module.new
73
+ end
74
+ end
75
+
76
+ def create_accessors_for(key)
77
+ accessors_module.module_eval <<-end_eval
78
+ def #{key.name}
79
+ read_key(:#{key.name})
80
+ end
81
+
82
+ def #{key.name}_before_typecast
83
+ read_key_before_typecast(:#{key.name})
84
+ end
85
+
86
+ def #{key.name}=(value)
87
+ write_key(:#{key.name}, value)
88
+ end
89
+
90
+ def #{key.name}?
91
+ read_key(:#{key.name}).present?
92
+ end
93
+ end_eval
94
+
95
+ include accessors_module
96
+ end
97
+
98
+ def create_key_in_descendants(*args)
99
+ return if descendants.blank?
100
+ descendants.each { |descendant| descendant.key(*args) }
101
+ end
102
+
103
+ def create_indexes_for(key)
104
+ ensure_index key.name if key.options[:index] && !key.embeddable?
105
+ end
106
+
107
+ def create_validations_for(key)
108
+ attribute = key.name.to_sym
109
+
110
+ if key.options[:required]
111
+ validates_presence_of(attribute)
112
+ end
113
+
114
+ if key.options[:unique]
115
+ validates_uniqueness_of(attribute)
116
+ end
117
+
118
+ if key.options[:numeric]
119
+ number_options = key.type == Integer ? {:only_integer => true} : {}
120
+ validates_numericality_of(attribute, number_options)
121
+ end
122
+
123
+ if key.options[:format]
124
+ validates_format_of(attribute, :with => key.options[:format])
125
+ end
126
+
127
+ if key.options[:in]
128
+ validates_inclusion_of(attribute, :within => key.options[:in])
129
+ end
130
+ if key.options[:within]
131
+ validates_inclusion_of(attribute, :within => key.options[:within])
132
+ end
133
+
134
+ if key.options[:not_in]
135
+ validates_exclusion_of(attribute, :within => key.options[:not_in])
136
+ end
137
+
138
+ if key.options[:length]
139
+ length_options = case key.options[:length]
140
+ when Integer
141
+ {:minimum => 0, :maximum => key.options[:length]}
142
+ when Range
143
+ {:within => key.options[:length]}
144
+ when Hash
145
+ key.options[:length]
146
+ end
147
+ validates_length_of(attribute, length_options)
148
+ end
149
+ end
150
+ end
151
+
152
+ module InstanceMethods
153
+ def new?
154
+ @new
155
+ end
156
+
157
+ def attributes=(attrs)
158
+ return if attrs.blank?
159
+
160
+ attrs.each_pair do |name, value|
161
+ writer_method = "#{name}="
162
+
163
+ if respond_to?(writer_method)
164
+ self.send(writer_method, value)
165
+ else
166
+ self[name.to_s] = value
167
+ end
168
+ end
169
+ end
170
+
171
+ def attributes
172
+ attrs = HashWithIndifferentAccess.new
173
+
174
+ keys.each_pair do |name, key|
175
+ value = key.set(self[key.name])
176
+ attrs[name] = value
177
+ end
178
+
179
+ embedded_associations.each do |association|
180
+ if documents = instance_variable_get(association.ivar)
181
+ attrs[association.name] = documents.map { |document| document.to_mongo }
182
+ end
183
+ end
184
+
185
+ attrs
186
+ end
187
+ alias :to_mongo :attributes
188
+
189
+ def assign(attrs={})
190
+ self.attributes = attrs
191
+ end
192
+
193
+ def update_attributes(attrs={})
194
+ assign(attrs)
195
+ save
196
+ end
197
+
198
+ def update_attributes!(attrs={})
199
+ assign(attrs)
200
+ save!
201
+ end
202
+
203
+ def id
204
+ _id
205
+ end
206
+
207
+ def id=(value)
208
+ if self.class.using_object_id?
209
+ value = MongoMapper.normalize_object_id(value)
210
+ end
211
+
212
+ self[:_id] = value
213
+ end
214
+
215
+ def [](name)
216
+ read_key(name)
217
+ end
218
+
219
+ def []=(name, value)
220
+ ensure_key_exists(name)
221
+ write_key(name, value)
222
+ end
223
+
224
+ # @api public
225
+ def keys
226
+ self.class.keys
227
+ end
228
+
229
+ # @api private?
230
+ def key_names
231
+ keys.keys
232
+ end
233
+
234
+ # @api private?
235
+ def non_embedded_keys
236
+ keys.values.select { |key| !key.embeddable? }
237
+ end
238
+
239
+ # @api private?
240
+ def embedded_keys
241
+ keys.values.select { |key| key.embeddable? }
242
+ end
243
+
244
+ private
245
+ def assign_type_if_present
246
+ self._type = self.class.name if respond_to?(:_type=)
247
+ end
248
+
249
+ def ensure_key_exists(name)
250
+ self.class.key(name) unless respond_to?("#{name}=")
251
+ end
252
+
253
+ def read_key(name)
254
+ if key = keys[name]
255
+ var_name = "@#{name}"
256
+ value = key.get(instance_variable_get(var_name))
257
+ instance_variable_set(var_name, value)
258
+ else
259
+ raise KeyNotFound, "Could not find key: #{name.inspect}"
260
+ end
261
+ end
262
+
263
+ def read_key_before_typecast(name)
264
+ instance_variable_get("@#{name}_before_typecast")
265
+ end
266
+
267
+ def write_key(name, value)
268
+ key = keys[name]
269
+ instance_variable_set "@#{name}_before_typecast", value
270
+ instance_variable_set "@#{name}", key.set(value)
271
+ end
272
+ end
273
+
274
+ class Key
275
+ attr_accessor :name, :type, :options, :default_value
276
+
277
+ def initialize(*args)
278
+ options = args.extract_options!
279
+ @name, @type = args.shift.to_s, args.shift
280
+ self.options = (options || {}).symbolize_keys
281
+ self.default_value = self.options.delete(:default)
282
+ end
283
+
284
+ def ==(other)
285
+ @name == other.name && @type == other.type
286
+ end
287
+
288
+ def embeddable?
289
+ type.respond_to?(:embeddable?) && type.embeddable? ? true : false
290
+ end
291
+
292
+ def number?
293
+ [Integer, Float].include?(type)
294
+ end
295
+
296
+ def get(value)
297
+ if value.nil? && !default_value.nil?
298
+ return default_value
299
+ end
300
+
301
+ type.from_mongo(value)
302
+ end
303
+
304
+ def set(value)
305
+ type.to_mongo(value)
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end