djsun-mongomapper 0.3.5.5 → 0.4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.rdoc +38 -38
  2. data/Rakefile +87 -73
  3. data/VERSION +1 -1
  4. data/lib/mongomapper.rb +67 -71
  5. data/lib/mongomapper/associations.rb +86 -84
  6. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -34
  7. data/lib/mongomapper/associations/many_embedded_proxy.rb +67 -17
  8. data/lib/mongomapper/associations/proxy.rb +74 -73
  9. data/lib/mongomapper/document.rb +342 -348
  10. data/lib/mongomapper/embedded_document.rb +354 -274
  11. data/lib/mongomapper/finder_options.rb +84 -84
  12. data/lib/mongomapper/key.rb +32 -76
  13. data/lib/mongomapper/rails_compatibility/document.rb +14 -14
  14. data/lib/mongomapper/rails_compatibility/embedded_document.rb +26 -24
  15. data/lib/mongomapper/support.rb +156 -29
  16. data/lib/mongomapper/validations.rb +69 -47
  17. data/test/custom_matchers.rb +48 -0
  18. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -56
  19. data/test/functional/associations/test_belongs_to_proxy.rb +48 -49
  20. data/test/functional/associations/test_many_documents_as_proxy.rb +208 -253
  21. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +130 -130
  22. data/test/functional/associations/test_many_embedded_proxy.rb +168 -106
  23. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -262
  24. data/test/functional/test_binary.rb +21 -0
  25. data/test/functional/test_document.rb +946 -952
  26. data/test/functional/test_embedded_document.rb +98 -0
  27. data/test/functional/test_pagination.rb +87 -80
  28. data/test/functional/test_rails_compatibility.rb +29 -29
  29. data/test/functional/test_validations.rb +262 -172
  30. data/test/models.rb +169 -169
  31. data/test/test_helper.rb +28 -66
  32. data/test/unit/serializers/test_json_serializer.rb +193 -193
  33. data/test/unit/test_document.rb +161 -123
  34. data/test/unit/test_embedded_document.rb +643 -547
  35. data/test/unit/test_finder_options.rb +183 -183
  36. data/test/unit/test_key.rb +175 -247
  37. data/test/unit/test_rails_compatibility.rb +38 -33
  38. data/test/unit/test_serializations.rb +52 -52
  39. data/test/unit/test_support.rb +268 -0
  40. data/test/unit/test_time_zones.rb +40 -0
  41. data/test/unit/test_validations.rb +499 -258
  42. metadata +22 -12
  43. data/History +0 -76
  44. data/mongomapper.gemspec +0 -145
@@ -1,274 +1,354 @@
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
- key :_id, String
18
- end
19
- end
20
-
21
- module ClassMethods
22
- def inherited(subclass)
23
- unless subclass.embeddable?
24
- subclass.collection(self.collection.name)
25
- end
26
-
27
- (@subclasses ||= []) << subclass
28
- end
29
-
30
- def subclasses
31
- @subclasses
32
- end
33
-
34
- def keys
35
- @keys ||= if parent = parent_model
36
- parent.keys.dup
37
- else
38
- HashWithIndifferentAccess.new
39
- end
40
- end
41
-
42
- def key(*args)
43
- key = Key.new(*args)
44
-
45
- if keys[key.name].blank?
46
- keys[key.name] = key
47
-
48
- create_accessors_for(key)
49
- add_to_subclasses(*args)
50
- apply_validations_for(key)
51
- create_indexes_for(key)
52
-
53
- key
54
- end
55
- end
56
-
57
- def add_to_subclasses(*args)
58
- return if subclasses.blank?
59
-
60
- subclasses.each do |subclass|
61
- subclass.key(*args)
62
- end
63
- end
64
-
65
- def ensure_index(name_or_array, options={})
66
- keys_to_index = if name_or_array.is_a?(Array)
67
- name_or_array.map { |pair| [pair[0], pair[1]] }
68
- else
69
- name_or_array
70
- end
71
-
72
- collection.create_index(keys_to_index, options.delete(:unique))
73
- end
74
-
75
- def embeddable?
76
- !self.ancestors.include?(Document)
77
- end
78
-
79
- def parent_model
80
- (ancestors - [self,EmbeddedDocument]).find do |parent_class|
81
- parent_class.ancestors.include?(EmbeddedDocument)
82
- end
83
- end
84
-
85
- private
86
- def accessors_module
87
- if const_defined?('MongoMapperKeys')
88
- const_get 'MongoMapperKeys'
89
- else
90
- const_set 'MongoMapperKeys', Module.new
91
- end
92
- end
93
-
94
- def create_accessors_for(key)
95
- accessors_module.module_eval <<-end_eval
96
- def #{key.name}
97
- read_attribute(:'#{key.name}')
98
- end
99
-
100
- def #{key.name}_before_typecast
101
- read_attribute_before_typecast(:'#{key.name}')
102
- end
103
-
104
- def #{key.name}=(value)
105
- write_attribute(:'#{key.name}', value)
106
- end
107
-
108
- def #{key.name}?
109
- read_attribute(:#{key.name}).present?
110
- end
111
- end_eval
112
- include accessors_module
113
- end
114
-
115
- def create_indexes_for(key)
116
- ensure_index key.name if key.options[:index]
117
- end
118
-
119
- def apply_validations_for(key)
120
- attribute = key.name.to_sym
121
-
122
- if key.options[:required]
123
- validates_presence_of(attribute)
124
- end
125
-
126
- if key.options[:unique]
127
- validates_uniqueness_of(attribute)
128
- end
129
-
130
- if key.options[:numeric]
131
- number_options = key.type == Integer ? {:only_integer => true} : {}
132
- validates_numericality_of(attribute, number_options)
133
- end
134
-
135
- if key.options[:format]
136
- validates_format_of(attribute, :with => key.options[:format])
137
- end
138
-
139
- if key.options[:length]
140
- length_options = case key.options[:length]
141
- when Integer
142
- {:minimum => 0, :maximum => key.options[:length]}
143
- when Range
144
- {:within => key.options[:length]}
145
- when Hash
146
- key.options[:length]
147
- end
148
- validates_length_of(attribute, length_options)
149
- end
150
- end
151
- end
152
-
153
- module InstanceMethods
154
- def initialize(attrs={})
155
- unless attrs.nil?
156
- self.class.associations.each_pair do |name, association|
157
- if collection = attrs.delete(name)
158
- send("#{association.name}=", collection)
159
- end
160
- end
161
-
162
- self.attributes = attrs
163
-
164
- if respond_to?(:_type=) && self['_type'].blank?
165
- self._type = self.class.name
166
- end
167
- end
168
-
169
- if self.class.embeddable? && read_attribute(:_id).blank?
170
- write_attribute :_id, Mongo::ObjectID.new.to_s
171
- end
172
- end
173
-
174
- def attributes=(attrs)
175
- return if attrs.blank?
176
- attrs.each_pair do |name, value|
177
- writer_method = "#{name}="
178
-
179
- if respond_to?(writer_method)
180
- self.send(writer_method, value)
181
- else
182
- self[name.to_s] = value
183
- end
184
- end
185
- end
186
-
187
- def attributes
188
- attrs = HashWithIndifferentAccess.new
189
- self.class.keys.each_pair do |name, key|
190
- value =
191
- if key.native?
192
- read_attribute(key.name)
193
- else
194
- if embedded_document = read_attribute(key.name)
195
- embedded_document.attributes
196
- end
197
- end
198
-
199
- attrs[name] = value
200
- end
201
- attrs.merge!(embedded_association_attributes)
202
- end
203
-
204
- # all attributes, even ones with null values
205
- def all_attributes
206
-
207
- end
208
-
209
- def [](name)
210
- read_attribute(name)
211
- end
212
-
213
- def []=(name, value)
214
- ensure_key_exists(name)
215
- write_attribute(name, value)
216
- end
217
-
218
- def ==(other)
219
- other.is_a?(self.class) && id == other.id
220
- end
221
-
222
- def id
223
- read_attribute(:_id)
224
- end
225
-
226
- def id=(value)
227
- @using_custom_id = true
228
- write_attribute :_id, value
229
- end
230
-
231
- def using_custom_id?
232
- !!@using_custom_id
233
- end
234
-
235
- def inspect
236
- attributes_as_nice_string = self.class.keys.keys.collect do |name|
237
- "#{name}: #{read_attribute(name)}"
238
- end.join(", ")
239
- "#<#{self.class} #{attributes_as_nice_string}>"
240
- end
241
-
242
- private
243
- def ensure_key_exists(name)
244
- self.class.key(name) unless respond_to?("#{name}=")
245
- end
246
-
247
- def read_attribute(name)
248
- value = self.class.keys[name].get(instance_variable_get("@#{name}"))
249
- instance_variable_set "@#{name}", value if !frozen?
250
- value
251
- end
252
-
253
- def read_attribute_before_typecast(name)
254
- instance_variable_get("@#{name}_before_typecast")
255
- end
256
-
257
- def write_attribute(name, value)
258
- instance_variable_set "@#{name}_before_typecast", value
259
- instance_variable_set "@#{name}", self.class.keys[name].set(value)
260
- end
261
-
262
- def embedded_association_attributes
263
- returning HashWithIndifferentAccess.new do |attrs|
264
- self.class.associations.each_pair do |name, association|
265
- next unless association.embeddable?
266
- next unless documents = instance_variable_get(association.ivar)
267
-
268
- attrs[name] = documents.collect { |doc| doc.attributes }
269
- end
270
- end
271
- end
272
- end # InstanceMethods
273
- end # EmbeddedDocument
274
- end # MongoMapper
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 inherited(subclass)
26
+ unless subclass.embeddable?
27
+ subclass.set_collection_name(collection_name)
28
+ end
29
+
30
+ (@subclasses ||= []) << subclass
31
+ end
32
+
33
+ def subclasses
34
+ @subclasses
35
+ end
36
+
37
+ def keys
38
+ @keys ||= if parent = parent_model
39
+ parent.keys.dup
40
+ else
41
+ HashWithIndifferentAccess.new
42
+ end
43
+ end
44
+
45
+ def key(*args)
46
+ key = Key.new(*args)
47
+
48
+ if keys[key.name].blank?
49
+ keys[key.name] = key
50
+
51
+ create_accessors_for(key)
52
+ add_to_subclasses(*args)
53
+ apply_validations_for(key)
54
+ create_indexes_for(key)
55
+
56
+ key
57
+ end
58
+ end
59
+
60
+ def add_to_subclasses(*args)
61
+ return if subclasses.blank?
62
+
63
+ subclasses.each do |subclass|
64
+ subclass.key(*args)
65
+ end
66
+ end
67
+
68
+ def ensure_index(name_or_array, options={})
69
+ keys_to_index = if name_or_array.is_a?(Array)
70
+ name_or_array.map { |pair| [pair[0], pair[1]] }
71
+ else
72
+ name_or_array
73
+ end
74
+
75
+ collection.create_index(keys_to_index, options.delete(:unique))
76
+ end
77
+
78
+ def embeddable?
79
+ !self.ancestors.include?(Document)
80
+ end
81
+
82
+ def parent_model
83
+ (ancestors - [self,EmbeddedDocument]).find do |parent_class|
84
+ parent_class.ancestors.include?(EmbeddedDocument)
85
+ end
86
+ end
87
+
88
+ def to_mongo(instance)
89
+ return nil if instance.nil?
90
+ instance.to_mongo
91
+ end
92
+
93
+ def from_mongo(instance_or_hash)
94
+ return nil if instance_or_hash.nil?
95
+
96
+ if instance_or_hash.is_a?(self)
97
+ instance_or_hash
98
+ else
99
+ new(instance_or_hash)
100
+ end
101
+ end
102
+
103
+ private
104
+ def accessors_module
105
+ if const_defined?('MongoMapperKeys')
106
+ const_get 'MongoMapperKeys'
107
+ else
108
+ const_set 'MongoMapperKeys', Module.new
109
+ end
110
+ end
111
+
112
+ def create_accessors_for(key)
113
+ accessors_module.module_eval <<-end_eval
114
+ def #{key.name}
115
+ read_attribute(:'#{key.name}')
116
+ end
117
+
118
+ def #{key.name}_before_typecast
119
+ read_attribute_before_typecast(:'#{key.name}')
120
+ end
121
+
122
+ def #{key.name}=(value)
123
+ write_attribute(:'#{key.name}', value)
124
+ end
125
+
126
+ def #{key.name}?
127
+ read_attribute(:#{key.name}).present?
128
+ end
129
+ end_eval
130
+ include accessors_module
131
+ end
132
+
133
+ def create_indexes_for(key)
134
+ ensure_index key.name if key.options[:index]
135
+ end
136
+
137
+ def apply_validations_for(key)
138
+ attribute = key.name.to_sym
139
+
140
+ if key.options[:required]
141
+ validates_presence_of(attribute)
142
+ end
143
+
144
+ if key.options[:unique]
145
+ validates_uniqueness_of(attribute)
146
+ end
147
+
148
+ if key.options[:numeric]
149
+ number_options = key.type == Integer ? {:only_integer => true} : {}
150
+ validates_numericality_of(attribute, number_options)
151
+ end
152
+
153
+ if key.options[:format]
154
+ validates_format_of(attribute, :with => key.options[:format])
155
+ end
156
+
157
+ if key.options[:length]
158
+ length_options = case key.options[:length]
159
+ when Integer
160
+ {:minimum => 0, :maximum => key.options[:length]}
161
+ when Range
162
+ {:within => key.options[:length]}
163
+ when Hash
164
+ key.options[:length]
165
+ end
166
+ validates_length_of(attribute, length_options)
167
+ end
168
+ end
169
+ end
170
+
171
+ module InstanceMethods
172
+ def initialize(attrs={})
173
+ unless attrs.nil?
174
+ self.class.associations.each_pair do |name, association|
175
+ if collection = attrs.delete(name)
176
+ if association.many? && association.klass.embeddable?
177
+ root_document = attrs[:_root_document] || self
178
+ collection.each do |doc|
179
+ doc[:_root_document] = root_document
180
+ end
181
+ end
182
+ send("#{association.name}=", collection)
183
+ end
184
+ end
185
+
186
+ self.attributes = attrs
187
+
188
+ if respond_to?(:_type=) && self['_type'].blank?
189
+ self._type = self.class.name
190
+ end
191
+ end
192
+
193
+ if self.class.embeddable?
194
+ if read_attribute(:_id).blank?
195
+ write_attribute :_id, Mongo::ObjectID.new.to_s
196
+ @new_document = true
197
+ else
198
+ @new_document = false
199
+ end
200
+ end
201
+ end
202
+
203
+ def new?
204
+ !!@new_document
205
+ end
206
+
207
+ def attributes=(attrs)
208
+ return if attrs.blank?
209
+ attrs.each_pair do |name, value|
210
+ writer_method = "#{name}="
211
+
212
+ if respond_to?(writer_method)
213
+ self.send(writer_method, value)
214
+ else
215
+ self[name.to_s] = value
216
+ end
217
+ end
218
+ end
219
+
220
+ def attributes
221
+ attrs = HashWithIndifferentAccess.new
222
+
223
+ embedded_keys.each do |key|
224
+ puts key.inspect
225
+ attrs[key.name] = read_attribute(key.name).try(:attributes)
226
+ end
227
+
228
+ non_embedded_keys.each do |key|
229
+ attrs[key.name] = read_attribute(key.name)
230
+ end
231
+
232
+ embedded_associations.each do |association|
233
+ documents = instance_variable_get(association.ivar)
234
+ next if documents.nil?
235
+ attrs[association.name] = documents.collect { |doc| doc.attributes }
236
+ end
237
+
238
+ attrs
239
+ end
240
+
241
+ def to_mongo
242
+ attrs = HashWithIndifferentAccess.new
243
+
244
+ _keys.each_pair do |name, key|
245
+ value = key.set(read_attribute(key.name))
246
+ attrs[name] = value
247
+ end
248
+
249
+ embedded_associations.each do |association|
250
+ if documents = instance_variable_get(association.ivar)
251
+ attrs[association.name] = documents.map { |document| document.to_mongo }
252
+ end
253
+ end
254
+
255
+ attrs
256
+ end
257
+
258
+ def clone
259
+ clone_attributes = self.attributes
260
+ clone_attributes.delete("_id")
261
+ self.class.new(clone_attributes)
262
+ end
263
+
264
+ def [](name)
265
+ read_attribute(name)
266
+ end
267
+
268
+ def []=(name, value)
269
+ ensure_key_exists(name)
270
+ write_attribute(name, value)
271
+ end
272
+
273
+ def ==(other)
274
+ other.is_a?(self.class) && id == other.id
275
+ end
276
+
277
+ def id
278
+ read_attribute(:_id)
279
+ end
280
+
281
+ def id=(value)
282
+ @using_custom_id = true
283
+ write_attribute :_id, value
284
+ end
285
+
286
+ def using_custom_id?
287
+ !!@using_custom_id
288
+ end
289
+
290
+ def inspect
291
+ attributes_as_nice_string = key_names.collect do |name|
292
+ "#{name}: #{read_attribute(name).inspect}"
293
+ end.join(", ")
294
+ "#<#{self.class} #{attributes_as_nice_string}>"
295
+ end
296
+
297
+ def save
298
+ if _root_document
299
+ _root_document.save
300
+ end
301
+ end
302
+
303
+ def update_attributes(attrs={})
304
+ self.attributes = attrs
305
+ save
306
+ end
307
+
308
+ private
309
+ def _keys
310
+ self.class.keys
311
+ end
312
+
313
+ def key_names
314
+ _keys.keys
315
+ end
316
+
317
+ def non_embedded_keys
318
+ _keys.values.select { |key| !key.embeddable? }
319
+ end
320
+
321
+ def embedded_keys
322
+ _keys.values.select { |key| key.embeddable? }
323
+ end
324
+
325
+ def ensure_key_exists(name)
326
+ self.class.key(name) unless respond_to?("#{name}=")
327
+ end
328
+
329
+ def read_attribute(name)
330
+ value = _keys[name].get(instance_variable_get("@#{name}"))
331
+ instance_variable_set "@#{name}", value if !frozen?
332
+ value
333
+ end
334
+
335
+ def read_attribute_before_typecast(name)
336
+ instance_variable_get("@#{name}_before_typecast")
337
+ end
338
+
339
+ def write_attribute(name, value)
340
+ key = _keys[name]
341
+ instance_variable_set "@#{name}_before_typecast", value
342
+ instance_variable_set "@#{name}", key.set(value)
343
+ end
344
+
345
+ def embedded_associations
346
+ self.class.associations.select do |name, association|
347
+ association.embeddable?
348
+ end.map do |name, association|
349
+ association
350
+ end
351
+ end
352
+ end # InstanceMethods
353
+ end # EmbeddedDocument
354
+ end # MongoMapper