mongo_mapper 0.12.0 → 0.13.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +35 -13
  3. data/bin/mmconsole +1 -1
  4. data/lib/mongo_mapper.rb +4 -0
  5. data/lib/mongo_mapper/connection.rb +17 -6
  6. data/lib/mongo_mapper/document.rb +1 -0
  7. data/lib/mongo_mapper/exceptions.rb +4 -1
  8. data/lib/mongo_mapper/extensions/binary.rb +1 -1
  9. data/lib/mongo_mapper/extensions/boolean.rb +20 -23
  10. data/lib/mongo_mapper/extensions/date.rb +3 -3
  11. data/lib/mongo_mapper/extensions/integer.rb +5 -1
  12. data/lib/mongo_mapper/extensions/kernel.rb +2 -0
  13. data/lib/mongo_mapper/extensions/ordered_hash.rb +23 -0
  14. data/lib/mongo_mapper/extensions/string.rb +2 -2
  15. data/lib/mongo_mapper/extensions/time.rb +7 -5
  16. data/lib/mongo_mapper/middleware/identity_map.rb +3 -4
  17. data/lib/mongo_mapper/plugins.rb +1 -1
  18. data/lib/mongo_mapper/plugins/associations.rb +11 -5
  19. data/lib/mongo_mapper/plugins/associations/base.rb +5 -3
  20. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +0 -0
  21. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +8 -8
  22. data/lib/mongo_mapper/plugins/associations/collection.rb +2 -0
  23. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +32 -7
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +2 -2
  25. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +12 -12
  26. data/lib/mongo_mapper/plugins/associations/proxy.rb +5 -1
  27. data/lib/mongo_mapper/plugins/associations/single_association.rb +6 -6
  28. data/lib/mongo_mapper/plugins/clone.rb +4 -2
  29. data/lib/mongo_mapper/plugins/dirty.rb +22 -21
  30. data/lib/mongo_mapper/plugins/document.rb +4 -4
  31. data/lib/mongo_mapper/plugins/dumpable.rb +22 -0
  32. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +58 -9
  33. data/lib/mongo_mapper/plugins/identity_map.rb +42 -32
  34. data/lib/mongo_mapper/plugins/keys.rb +133 -54
  35. data/lib/mongo_mapper/plugins/keys/key.rb +68 -22
  36. data/lib/mongo_mapper/plugins/modifiers.rb +26 -19
  37. data/lib/mongo_mapper/plugins/persistence.rb +15 -5
  38. data/lib/mongo_mapper/plugins/querying.rb +15 -40
  39. data/lib/mongo_mapper/plugins/querying/{decorator.rb → decorated_plucky_query.rb} +24 -4
  40. data/lib/mongo_mapper/plugins/rails.rb +22 -2
  41. data/lib/mongo_mapper/plugins/safe.rb +8 -5
  42. data/lib/mongo_mapper/plugins/sci.rb +26 -4
  43. data/lib/mongo_mapper/plugins/scopes.rb +5 -4
  44. data/lib/mongo_mapper/plugins/timestamps.rb +11 -4
  45. data/lib/mongo_mapper/plugins/validations.rb +1 -1
  46. data/lib/mongo_mapper/utils.rb +12 -0
  47. data/lib/mongo_mapper/version.rb +1 -1
  48. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +20 -7
  49. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +6 -0
  50. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +18 -1
  51. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +9 -5
  52. data/{test/functional/test_accessible.rb → spec/functional/accessible_spec.rb} +29 -29
  53. data/{test/functional/associations/test_belongs_to_polymorphic_proxy.rb → spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb} +10 -10
  54. data/{test/functional/associations/test_belongs_to_proxy.rb → spec/functional/associations/belongs_to_proxy_spec.rb} +82 -64
  55. data/{test/functional/associations/test_in_array_proxy.rb → spec/functional/associations/in_array_proxy_spec.rb} +68 -68
  56. data/{test/functional/associations/test_many_documents_as_proxy.rb → spec/functional/associations/many_documents_as_proxy_spec.rb} +37 -38
  57. data/{test/functional/associations/test_many_documents_proxy.rb → spec/functional/associations/many_documents_proxy_spec.rb} +233 -146
  58. data/{test/functional/associations/test_many_embedded_polymorphic_proxy.rb → spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb} +19 -20
  59. data/{test/functional/associations/test_many_embedded_proxy.rb → spec/functional/associations/many_embedded_proxy_spec.rb} +23 -24
  60. data/{test/functional/associations/test_many_polymorphic_proxy.rb → spec/functional/associations/many_polymorphic_proxy_spec.rb} +45 -46
  61. data/{test/functional/associations/test_one_as_proxy.rb → spec/functional/associations/one_as_proxy_spec.rb} +75 -77
  62. data/{test/functional/associations/test_one_embedded_polymorphic_proxy.rb → spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb} +31 -32
  63. data/{test/functional/associations/test_one_embedded_proxy.rb → spec/functional/associations/one_embedded_proxy_spec.rb} +10 -10
  64. data/{test/functional/associations/test_one_proxy.rb → spec/functional/associations/one_proxy_spec.rb} +125 -102
  65. data/spec/functional/associations_spec.rb +48 -0
  66. data/{test/functional/test_binary.rb → spec/functional/binary_spec.rb} +6 -6
  67. data/spec/functional/caching_spec.rb +75 -0
  68. data/{test/functional/test_callbacks.rb → spec/functional/callbacks_spec.rb} +84 -26
  69. data/{test/functional/test_dirty.rb → spec/functional/dirty_spec.rb} +57 -42
  70. data/{test/functional/test_document.rb → spec/functional/document_spec.rb} +52 -52
  71. data/spec/functional/dumpable_spec.rb +24 -0
  72. data/{test/functional/test_dynamic_querying.rb → spec/functional/dynamic_querying_spec.rb} +14 -14
  73. data/{test/functional/test_embedded_document.rb → spec/functional/embedded_document_spec.rb} +51 -42
  74. data/{test/functional/test_equality.rb → spec/functional/equality_spec.rb} +4 -4
  75. data/spec/functional/extensions_spec.rb +16 -0
  76. data/{test/functional/test_identity_map.rb → spec/functional/identity_map_spec.rb} +73 -61
  77. data/spec/functional/indexes_spec.rb +48 -0
  78. data/spec/functional/keys_spec.rb +224 -0
  79. data/{test/functional/test_logger.rb → spec/functional/logger_spec.rb} +6 -6
  80. data/spec/functional/modifiers_spec.rb +550 -0
  81. data/spec/functional/pagination_spec.rb +89 -0
  82. data/spec/functional/protected_spec.rb +199 -0
  83. data/spec/functional/querying_spec.rb +1003 -0
  84. data/spec/functional/rails_spec.rb +55 -0
  85. data/spec/functional/safe_spec.rb +163 -0
  86. data/{test/functional/test_sci.rb → spec/functional/sci_spec.rb} +123 -34
  87. data/{test/functional/test_scopes.rb → spec/functional/scopes_spec.rb} +59 -26
  88. data/spec/functional/timestamps_spec.rb +97 -0
  89. data/{test/functional/test_touch.rb → spec/functional/touch_spec.rb} +13 -13
  90. data/spec/functional/userstamps_spec.rb +46 -0
  91. data/{test/functional/test_validations.rb → spec/functional/validations_spec.rb} +64 -64
  92. data/spec/spec_helper.rb +81 -0
  93. data/spec/support/matchers.rb +24 -0
  94. data/{test → spec/support}/models.rb +1 -6
  95. data/spec/unit/associations/base_spec.rb +146 -0
  96. data/spec/unit/associations/belongs_to_association_spec.rb +30 -0
  97. data/spec/unit/associations/many_association_spec.rb +64 -0
  98. data/spec/unit/associations/one_association_spec.rb +48 -0
  99. data/{test/unit/associations/test_proxy.rb → spec/unit/associations/proxy_spec.rb} +21 -21
  100. data/{test/unit/test_clone.rb → spec/unit/clone_spec.rb} +21 -11
  101. data/spec/unit/config_generator_spec.rb +24 -0
  102. data/{test/unit/test_document.rb → spec/unit/document_spec.rb} +42 -42
  103. data/{test/unit/test_dynamic_finder.rb → spec/unit/dynamic_finder_spec.rb} +28 -28
  104. data/{test/unit/test_embedded_document.rb → spec/unit/embedded_document_spec.rb} +102 -108
  105. data/{test/unit/test_equality.rb → spec/unit/equality_spec.rb} +7 -7
  106. data/{test/unit/test_exceptions.rb → spec/unit/exceptions_spec.rb} +3 -3
  107. data/{test/unit/test_extensions.rb → spec/unit/extensions_spec.rb} +85 -71
  108. data/spec/unit/identity_map_middleware_spec.rb +134 -0
  109. data/{test/unit/test_inspect.rb → spec/unit/inspect_spec.rb} +8 -8
  110. data/{test/unit/test_key.rb → spec/unit/key_spec.rb} +82 -52
  111. data/spec/unit/keys_spec.rb +155 -0
  112. data/spec/unit/model_generator_spec.rb +47 -0
  113. data/spec/unit/mongo_mapper_spec.rb +184 -0
  114. data/spec/unit/pagination_spec.rb +11 -0
  115. data/{test/unit/test_plugins.rb → spec/unit/plugins_spec.rb} +14 -14
  116. data/spec/unit/rails_compatibility_spec.rb +40 -0
  117. data/{test/unit/test_rails_reflect_on_association.rb → spec/unit/rails_reflect_on_association_spec.rb} +9 -9
  118. data/{test/unit/test_rails.rb → spec/unit/rails_spec.rb} +31 -31
  119. data/spec/unit/serialization_spec.rb +169 -0
  120. data/spec/unit/serializers/json_serializer_spec.rb +218 -0
  121. data/spec/unit/serializers/xml_serializer_spec.rb +198 -0
  122. data/{test/unit/test_time_zones.rb → spec/unit/time_zones_spec.rb} +8 -8
  123. data/{test/unit/test_translation.rb → spec/unit/translation_spec.rb} +6 -6
  124. data/{test/unit/test_validations.rb → spec/unit/validations_spec.rb} +72 -59
  125. metadata +199 -179
  126. data/test/_NOTE_ON_TESTING +0 -1
  127. data/test/functional/test_associations.rb +0 -46
  128. data/test/functional/test_caching.rb +0 -77
  129. data/test/functional/test_indexes.rb +0 -50
  130. data/test/functional/test_modifiers.rb +0 -537
  131. data/test/functional/test_pagination.rb +0 -91
  132. data/test/functional/test_protected.rb +0 -201
  133. data/test/functional/test_querying.rb +0 -935
  134. data/test/functional/test_safe.rb +0 -76
  135. data/test/functional/test_timestamps.rb +0 -62
  136. data/test/functional/test_userstamps.rb +0 -44
  137. data/test/support/railtie.rb +0 -4
  138. data/test/support/railtie/autoloaded.rb +0 -2
  139. data/test/support/railtie/not_autoloaded.rb +0 -3
  140. data/test/support/railtie/parent.rb +0 -3
  141. data/test/test_active_model_lint.rb +0 -18
  142. data/test/test_helper.rb +0 -93
  143. data/test/unit/associations/test_base.rb +0 -146
  144. data/test/unit/associations/test_belongs_to_association.rb +0 -29
  145. data/test/unit/associations/test_many_association.rb +0 -63
  146. data/test/unit/associations/test_one_association.rb +0 -47
  147. data/test/unit/serializers/test_json_serializer.rb +0 -216
  148. data/test/unit/serializers/test_xml_serializer.rb +0 -196
  149. data/test/unit/test_identity_map_middleware.rb +0 -132
  150. data/test/unit/test_keys.rb +0 -65
  151. data/test/unit/test_mongo_mapper.rb +0 -157
  152. data/test/unit/test_pagination.rb +0 -11
  153. data/test/unit/test_rails_compatibility.rb +0 -38
  154. data/test/unit/test_serialization.rb +0 -166
@@ -57,37 +57,10 @@ module MongoMapper
57
57
  IdentityMap.repository[IdentityMap.key(self, id)]
58
58
  end
59
59
 
60
- module IdentityMapQueryMethods
61
- def all(opts={})
62
- query = clone.amend(opts)
63
- super.tap do |docs|
64
- model.remove_documents_from_map(docs) if query.fields?
65
- end
66
- end
67
-
68
- def find_one(opts={})
69
- query = clone.amend(opts)
70
-
71
- if IdentityMap.enabled? && query.simple? && (document = model.get_from_identity_map(query[:_id]))
72
- document
73
- else
74
- super.tap do |doc|
75
- model.remove_documents_from_map(doc) if query.fields?
76
- end
77
- end
78
- end
79
-
80
- def find_each(opts={}, &block)
81
- query = clone.amend(opts)
82
- super(opts) do |doc|
83
- model.remove_documents_from_map(doc) if query.fields?
84
- block.call(doc) unless block.nil?
85
- end
86
- end
87
- end
88
-
89
60
  def query(opts={})
90
- super.extend(IdentityMapQueryMethods)
61
+ super.tap do |query|
62
+ query.identity_map = self if Thread.current[:mongo_mapper_identity_map_enabled]
63
+ end
91
64
  end
92
65
 
93
66
  def remove_documents_from_map(*documents)
@@ -97,10 +70,11 @@ module MongoMapper
97
70
  end
98
71
 
99
72
  def load(attrs)
100
- return nil if attrs.nil?
73
+ return super unless Thread.current[:mongo_mapper_identity_map_enabled]
74
+ return nil unless attrs
101
75
  document = get_from_identity_map(attrs['_id'])
102
76
 
103
- if document.nil?
77
+ if !document
104
78
  document = super
105
79
  document.add_to_identity_map
106
80
  end
@@ -133,3 +107,39 @@ module MongoMapper
133
107
  end
134
108
  end
135
109
  end
110
+
111
+ module PluckyMethods
112
+ module ClassMethods
113
+ extend ActiveSupport::Concern
114
+
115
+ included do
116
+ attr_accessor :identity_map
117
+
118
+ # Ensure that these aliased methods in plucky also get overridden.
119
+ alias_method :first, :find_one
120
+ alias_method :each, :find_each
121
+ end
122
+
123
+ def find_one(opts={})
124
+ query = clone.amend(opts)
125
+
126
+ if identity_map && query.simple? && (document = identity_map.get_from_identity_map(query[:_id]))
127
+ document
128
+ else
129
+ super.tap do |doc|
130
+ doc.remove_from_identity_map if doc && query.fields?
131
+ end
132
+ end
133
+ end
134
+
135
+ def find_each(opts={})
136
+ query = clone.amend(opts)
137
+ super(opts) do |doc|
138
+ doc.remove_from_identity_map if doc && query.fields?
139
+ yield doc if block_given?
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ ::MongoMapper::Plugins::Querying::DecoratedPluckyQuery.send :include, ::PluckyMethods::ClassMethods
@@ -6,6 +6,8 @@ module MongoMapper
6
6
  module Keys
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ IS_RUBY_1_9 = method(:const_defined?).arity == 1
10
+
9
11
  included do
10
12
  extend ActiveSupport::DescendantsTracker
11
13
  key :_id, ObjectId, :default => lambda { BSON::ObjectId.new }
@@ -21,18 +23,37 @@ module MongoMapper
21
23
  @keys ||= {}
22
24
  end
23
25
 
26
+ def dynamic_keys
27
+ @dynamic_keys ||= Hash[*unaliased_keys.select {|k, v| v.dynamic? }.flatten(1)]
28
+ end
29
+
30
+ def defined_keys
31
+ @defined_keys ||= Hash[*unaliased_keys.select {|k, v| !v.dynamic? }.flatten(1)]
32
+ end
33
+
34
+ def unaliased_keys
35
+ @unaliased_keys ||= Hash[*keys.select {|k, v| k == v.name }.flatten(1)]
36
+ end
37
+
24
38
  def key(*args)
25
39
  Key.new(*args).tap do |key|
26
40
  keys[key.name] = key
27
- create_accessors_for(key)
41
+ keys[key.abbr] = key if key.abbr
42
+ create_accessors_for(key) if key.valid_ruby_name?
28
43
  create_key_in_descendants(*args)
29
44
  create_indexes_for(key)
30
45
  create_validations_for(key)
46
+ @dynamic_keys = @defined_keys = @unaliased_keys = @object_id_keys = nil
31
47
  end
32
48
  end
33
49
 
50
+ def persisted_name(name)
51
+ keys[name.to_s].persisted_name
52
+ end
53
+ alias_method :abbr, :persisted_name
54
+
34
55
  def key?(key)
35
- keys.keys.include?(key.to_s)
56
+ keys.key? key.to_s
36
57
  end
37
58
 
38
59
  def using_object_id?
@@ -40,7 +61,7 @@ module MongoMapper
40
61
  end
41
62
 
42
63
  def object_id_keys
43
- keys.keys.select { |key| keys[key].type == ObjectId }.map { |k| k.to_sym }
64
+ @object_id_keys ||= unaliased_keys.keys.select { |key| keys[key].type == ObjectId }.map(&:to_sym)
44
65
  end
45
66
 
46
67
  def object_id_key?(name)
@@ -48,20 +69,18 @@ module MongoMapper
48
69
  end
49
70
 
50
71
  def to_mongo(instance)
51
- return nil if instance.nil?
52
- instance.to_mongo
72
+ instance && instance.to_mongo
53
73
  end
54
74
 
55
75
  def from_mongo(value)
56
- return nil if value.nil?
57
- value.is_a?(self) ? value : load(value)
76
+ value && (value.instance_of?(self) ? value : load(value))
58
77
  end
59
78
 
60
79
  # load is overridden in identity map to ensure same objects are loaded
61
80
  def load(attrs)
62
81
  return nil if attrs.nil?
63
82
  begin
64
- attrs['_type'].present? ? attrs['_type'].constantize : self
83
+ attrs['_type'] ? attrs['_type'].constantize : self
65
84
  rescue NameError
66
85
  self
67
86
  end.allocate.initialize_from_database(attrs)
@@ -69,11 +88,13 @@ module MongoMapper
69
88
 
70
89
  private
71
90
  def key_accessors_module_defined?
72
- if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
91
+ # :nocov:
92
+ if IS_RUBY_1_9
73
93
  const_defined?('MongoMapperKeys')
74
94
  else
75
95
  const_defined?('MongoMapperKeys', false)
76
96
  end
97
+ # :nocov:
77
98
  end
78
99
 
79
100
  def accessors_module
@@ -90,10 +111,6 @@ module MongoMapper
90
111
  read_key(:#{key.name})
91
112
  end
92
113
 
93
- def #{key.name}_before_type_cast
94
- read_key_before_type_cast(:#{key.name})
95
- end
96
-
97
114
  def #{key.name}=(value)
98
115
  write_key(:#{key.name}, value)
99
116
  end
@@ -103,6 +120,12 @@ module MongoMapper
103
120
  end
104
121
  end_eval
105
122
 
123
+ if block_given?
124
+ accessors_module.module_eval do
125
+ yield
126
+ end
127
+ end
128
+
106
129
  include accessors_module
107
130
  end
108
131
 
@@ -165,11 +188,16 @@ module MongoMapper
165
188
 
166
189
  def initialize(attrs={})
167
190
  @_new = true
191
+ init_ivars
192
+ initialize_default_values(attrs)
168
193
  self.attributes = attrs
194
+ yield self if block_given?
169
195
  end
170
196
 
171
197
  def initialize_from_database(attrs={})
172
198
  @_new = false
199
+ init_ivars
200
+ initialize_default_values(attrs)
173
201
  load_from_database(attrs)
174
202
  self
175
203
  end
@@ -179,7 +207,7 @@ module MongoMapper
179
207
  end
180
208
 
181
209
  def attributes=(attrs)
182
- return if attrs.blank?
210
+ return if attrs == nil || attrs.blank?
183
211
 
184
212
  attrs.each_pair do |key, value|
185
213
  if respond_to?(:"#{key}=")
@@ -190,25 +218,30 @@ module MongoMapper
190
218
  end
191
219
  end
192
220
 
193
- def attributes
194
- HashWithIndifferentAccess.new.tap do |attrs|
195
- keys.select { |name,key| !self[key.name].nil? || key.type == ObjectId }.each do |name, key|
196
- value = key.set(self[key.name])
197
- attrs[name] = value
221
+ def to_mongo(include_abbreviatons = true)
222
+ BSON::OrderedHash.new.tap do |attrs|
223
+ self.class.unaliased_keys.each do |name, key|
224
+ value = self.read_key(key.name)
225
+ if key.type == ObjectId || !value.nil?
226
+ attrs[include_abbreviatons && key.persisted_name || name] = key.set(value)
227
+ end
198
228
  end
199
229
 
200
230
  embedded_associations.each do |association|
201
231
  if documents = instance_variable_get(association.ivar)
202
- if association.is_a?(Associations::OneAssociation)
232
+ if association.instance_of?(Associations::OneAssociation)
203
233
  attrs[association.name] = documents.to_mongo
204
234
  else
205
- attrs[association.name] = documents.map { |document| document.to_mongo }
235
+ attrs[association.name] = documents.map(&:to_mongo)
206
236
  end
207
237
  end
208
238
  end
209
239
  end
210
240
  end
211
- alias :to_mongo :attributes
241
+
242
+ def attributes
243
+ to_mongo(false).with_indifferent_access
244
+ end
212
245
 
213
246
  def assign(attrs={})
214
247
  warn "[DEPRECATION] #assign is deprecated, use #attributes="
@@ -231,7 +264,7 @@ module MongoMapper
231
264
  end
232
265
 
233
266
  def id
234
- _id
267
+ self[:_id]
235
268
  end
236
269
 
237
270
  def id=(value)
@@ -242,72 +275,118 @@ module MongoMapper
242
275
  self[:_id] = value
243
276
  end
244
277
 
245
- def [](name)
246
- read_key(name)
278
+ def keys
279
+ self.class.keys
247
280
  end
248
281
 
249
- def []=(name, value)
250
- ensure_key_exists(name)
251
- write_key(name, value)
282
+ def read_key(key_name)
283
+ key_name_sym = key_name.to_sym
284
+ if @_dynamic_attributes && @_dynamic_attributes.key?(key_name_sym)
285
+ @_dynamic_attributes[key_name_sym]
286
+ elsif key = keys[key_name.to_s]
287
+ if key.ivar && instance_variable_defined?(key.ivar)
288
+ value = instance_variable_get(key.ivar)
289
+ else
290
+ if key.ivar
291
+ instance_variable_set key.ivar, key.get(nil)
292
+ else
293
+ @_dynamic_attributes[key_name_sym] = key.get(nil)
294
+ end
295
+ end
296
+ end
252
297
  end
253
298
 
254
- def keys
255
- self.class.keys
299
+ alias_method :[], :read_key
300
+ alias_method :attribute, :read_key
301
+
302
+ def []=(name, value)
303
+ write_key(name, value)
256
304
  end
257
305
 
258
306
  def key_names
259
- keys.keys
307
+ @key_names ||= keys.keys
260
308
  end
261
309
 
262
310
  def non_embedded_keys
263
- keys.values.select { |key| !key.embeddable? }
311
+ @non_embedded_keys ||= keys.values.select { |key| !key.embeddable? }
264
312
  end
265
313
 
266
314
  def embedded_keys
267
- keys.values.select { |key| key.embeddable? }
315
+ @embedded_keys ||= keys.values.select(&:embeddable?)
316
+ end
317
+
318
+ protected
319
+
320
+ def unalias_key(name)
321
+ name = name.to_s
322
+ if key = keys[name]
323
+ key.name
324
+ else
325
+ name
326
+ end
268
327
  end
269
328
 
270
329
  private
330
+
331
+ def init_ivars
332
+ @__mm_keys = self.class.keys # Not dumpable
333
+ @__mm_default_keys = @__mm_keys.values.select(&:default?) # Not dumpable
334
+ @_dynamic_attributes = {} # Dumpable
335
+ end
336
+
271
337
  def load_from_database(attrs)
272
- return if attrs.blank?
338
+ return if attrs == nil || attrs.blank?
339
+
273
340
  attrs.each do |key, value|
274
- if respond_to?(:"#{key}=") && !self.class.key?(key)
341
+ if !@__mm_keys.key?(key) && respond_to?(:"#{key}=")
275
342
  self.send(:"#{key}=", value)
276
343
  else
277
- self[key] = value
344
+ internal_write_key key, value, false
278
345
  end
279
346
  end
280
347
  end
281
348
 
282
- def ensure_key_exists(name)
283
- self.class.key(name) unless respond_to?("#{name}=")
284
- end
285
-
286
349
  def set_parent_document(key, value)
287
- if key.embeddable? && value.is_a?(key.type)
350
+ if key.type and value.instance_of?(key.type) && key.embeddable? && value.respond_to?(:_parent_document)
288
351
  value._parent_document = self
289
352
  end
290
353
  end
291
354
 
292
- def read_key(key_name)
293
- if key = keys[key_name.to_s]
294
- value = key.get(instance_variable_get(:"@#{key_name}"))
295
- set_parent_document(key, value)
296
- instance_variable_set(:"@#{key_name}", value)
355
+ # This exists to be patched over by plugins, while letting us still get to the undecorated
356
+ # version of the method.
357
+ def write_key(name, value)
358
+ init_ivars unless @__mm_keys
359
+ internal_write_key(name.to_s, value)
360
+ end
361
+
362
+ def internal_write_key(name, value, cast = true)
363
+ key = @__mm_keys[name] || dynamic_key(name)
364
+ as_mongo = cast ? key.set(value) : value
365
+ as_typecast = key.get(as_mongo)
366
+ if key.ivar
367
+ if key.embeddable?
368
+ set_parent_document(key, value)
369
+ set_parent_document(key, as_typecast)
370
+ end
371
+ instance_variable_set key.ivar, as_typecast
372
+ else
373
+ @_dynamic_attributes[key.name.to_sym] = as_typecast
297
374
  end
375
+ @attributes = nil
298
376
  end
299
377
 
300
- def read_key_before_type_cast(name)
301
- instance_variable_get(:"@#{name}_before_type_cast")
378
+ def dynamic_key(name)
379
+ self.class.key(name, :__dynamic => true)
302
380
  end
303
381
 
304
- def write_key(name, value)
305
- key = keys[name.to_s]
306
- set_parent_document(key, value)
307
- instance_variable_set :"@#{name}_before_type_cast", value
308
- instance_variable_set :"@#{name}", key.set(value)
382
+ def initialize_default_values(except = {})
383
+ @__mm_default_keys.each do |key|
384
+ if !(except && except.key?(key.name))
385
+ internal_write_key key.name, key.default_value, false
386
+ end
387
+ end
309
388
  end
389
+ #end private
310
390
  end
311
391
  end
312
392
  end
313
-
@@ -3,58 +3,104 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Keys
5
5
  class Key
6
- attr_accessor :name, :type, :options, :default_value
6
+ attr_accessor :name, :type, :options, :default, :ivar, :abbr
7
+
8
+ ID_STR = '_id'
7
9
 
8
10
  def initialize(*args)
9
- options = args.extract_options!
11
+ options_from_args = args.extract_options!
10
12
  @name, @type = args.shift.to_s, args.shift
11
- self.options = (options || {}).symbolize_keys
12
- self.default_value = self.options[:default]
13
+ self.options = (options_from_args || {}).symbolize_keys
14
+ @dynamic = !!options[:__dynamic]
15
+ @embeddable = type.respond_to?(:embeddable?) ? type.embeddable? : false
16
+ @is_id = @name == ID_STR
17
+ @typecast = @options[:typecast]
18
+ @has_default = !!options.key?(:default)
19
+ self.default = self.options[:default] if default?
20
+
21
+ if abbr = @options[:abbr] || @options[:alias] || @options[:field_name]
22
+ @abbr = abbr.to_s
23
+ elsif @name.match(/^[A-Z]/) and !dynamic?
24
+ @abbr = @name
25
+ @name = @name.gsub(/^([A-Z])/) {|m| m.downcase }
26
+ Kernel.warn "Key names may not start with uppercase letters. If your field starts " +
27
+ "with an uppercase letter, use :field_name to specify the real field name. " +
28
+ "Accessors called `#{@name}` have been created instead."
29
+ end
30
+ @ivar = :"@#{name}" if valid_ruby_name?
31
+ validate_key_name! unless dynamic?
32
+ end
33
+
34
+ def persisted_name
35
+ @abbr || @name
13
36
  end
14
37
 
15
38
  def ==(other)
16
- @name == other.name && @type == other.type
39
+ @name == other.name && @type == other.type && @abbr == other.abbr
17
40
  end
18
41
 
19
42
  def embeddable?
20
- return false unless type.respond_to?(:embeddable?)
21
- type.embeddable?
43
+ @embeddable
22
44
  end
23
45
 
24
46
  def number?
25
47
  type == Integer || type == Float
26
48
  end
27
49
 
50
+ def default?
51
+ @has_default
52
+ end
53
+
54
+ def dynamic?
55
+ @dynamic
56
+ end
57
+
28
58
  def get(value)
29
- if value.nil? && !default_value.nil?
30
- if default_value.respond_to?(:call)
31
- return default_value.call
32
- else
33
- # Using Marshal is easiest way to get a copy of mutable objects
34
- # without getting an error on immutable objects
35
- return Marshal.load(Marshal.dump(default_value))
36
- end
37
- end
59
+ # Special Case: Generate default _id on access
60
+ value = default_value if @is_id and !value
38
61
 
39
- if options[:typecast].present?
40
- type.from_mongo(value).map! { |v| typecast_class.from_mongo(v) }
62
+ if @typecast
63
+ klass = typecast_class # Don't make this lookup on every call
64
+ type.from_mongo(value).map! { |v| klass.from_mongo(v) }
41
65
  else
42
66
  type.from_mongo(value)
43
67
  end
44
68
  end
45
69
 
46
70
  def set(value)
47
- type.to_mongo(value).tap do |values|
48
- if options[:typecast].present?
49
- values.map! { |v| typecast_class.to_mongo(v) }
50
- end
71
+ # Avoid tap here so we don't have to create a block binding.
72
+ values = type.to_mongo(value)
73
+ values.map! { |v| typecast_class.to_mongo(v) } if @typecast
74
+ values
75
+ end
76
+
77
+ def default_value
78
+ return unless default?
79
+ if default.instance_of? Proc
80
+ type.to_mongo default.call
81
+ else
82
+ # Using Marshal is easiest way to get a copy of mutable objects
83
+ # without getting an error on immutable objects
84
+ type.to_mongo Marshal.load(Marshal.dump(default))
51
85
  end
52
86
  end
53
87
 
88
+ def valid_ruby_name?
89
+ !!@name.match(/\A[a-z_][a-z0-9_]*\z/i)
90
+ end
91
+
54
92
  private
55
93
  def typecast_class
56
94
  @typecast_class ||= options[:typecast].constantize
57
95
  end
96
+
97
+ def validate_key_name!
98
+ if %w( id ).include? @name
99
+ raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)")
100
+ elsif !valid_ruby_name?
101
+ raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*")
102
+ end
103
+ end
58
104
  end
59
105
  end
60
106
  end