mongo_mapper 0.6.10 → 0.7.0

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