mongo_mapper 0.12.0 → 0.13.0.beta1

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 (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