mrkurt-mongo_mapper 0.6.8
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.
- data/.gitignore +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +38 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper.rb +139 -0
- data/lib/mongo_mapper/associations.rb +72 -0
- data/lib/mongo_mapper/associations/base.rb +113 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
- data/lib/mongo_mapper/associations/collection.rb +19 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/one_proxy.rb +61 -0
- data/lib/mongo_mapper/associations/proxy.rb +111 -0
- data/lib/mongo_mapper/callbacks.rb +61 -0
- data/lib/mongo_mapper/dirty.rb +117 -0
- data/lib/mongo_mapper/document.rb +496 -0
- data/lib/mongo_mapper/dynamic_finder.rb +74 -0
- data/lib/mongo_mapper/embedded_document.rb +380 -0
- data/lib/mongo_mapper/finder_options.rb +145 -0
- data/lib/mongo_mapper/key.rb +36 -0
- data/lib/mongo_mapper/mongo_mapper.rb +125 -0
- data/lib/mongo_mapper/pagination.rb +66 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +28 -0
- data/lib/mongo_mapper/serialization.rb +54 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +48 -0
- data/lib/mongo_mapper/support.rb +192 -0
- data/lib/mongo_mapper/validations.rb +39 -0
- data/mongo_mapper.gemspec +173 -0
- data/specs.watchr +30 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +91 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
- data/test/functional/associations/test_many_documents_proxy.rb +477 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
- data/test/functional/associations/test_one_proxy.rb +131 -0
- data/test/functional/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +33 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_dirty.rb +159 -0
- data/test/functional/test_document.rb +1198 -0
- data/test/functional/test_embedded_document.rb +135 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_modifiers.rb +242 -0
- data/test/functional/test_pagination.rb +95 -0
- data/test/functional/test_rails_compatibility.rb +25 -0
- data/test/functional/test_string_id_compatibility.rb +72 -0
- data/test/functional/test_validations.rb +361 -0
- data/test/models.rb +271 -0
- data/test/support/custom_matchers.rb +55 -0
- data/test/support/timing.rb +16 -0
- data/test/test_helper.rb +27 -0
- data/test/unit/associations/test_base.rb +182 -0
- data/test/unit/associations/test_proxy.rb +91 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_document.rb +236 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +709 -0
- data/test/unit/test_finder_options.rb +325 -0
- data/test/unit/test_key.rb +172 -0
- data/test/unit/test_mongo_mapper.rb +65 -0
- data/test/unit/test_pagination.rb +119 -0
- data/test/unit/test_rails_compatibility.rb +52 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +346 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +239 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
# @api private
|
3
|
+
module Finders
|
4
|
+
def dynamic_find(finder, args)
|
5
|
+
attributes = {}
|
6
|
+
finder.attributes.each_with_index do |attr, index|
|
7
|
+
attributes[attr] = args[index]
|
8
|
+
end
|
9
|
+
|
10
|
+
options = args.extract_options!.merge(attributes)
|
11
|
+
|
12
|
+
if result = find(finder.finder, options)
|
13
|
+
result
|
14
|
+
else
|
15
|
+
if finder.raise?
|
16
|
+
raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
if finder.instantiator
|
20
|
+
self.send(finder.instantiator, attributes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def method_missing(method, *args, &block)
|
27
|
+
finder = DynamicFinder.new(method)
|
28
|
+
|
29
|
+
if finder.found?
|
30
|
+
dynamic_find(finder, args)
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DynamicFinder
|
38
|
+
attr_reader :method, :attributes, :finder, :bang, :instantiator
|
39
|
+
|
40
|
+
def initialize(method)
|
41
|
+
@method = method
|
42
|
+
@finder = :first
|
43
|
+
@bang = false
|
44
|
+
match
|
45
|
+
end
|
46
|
+
|
47
|
+
def found?
|
48
|
+
@finder.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def raise?
|
52
|
+
bang == true
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def match
|
57
|
+
case method.to_s
|
58
|
+
when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
|
59
|
+
@finder = :all if $1 == 'all_by'
|
60
|
+
names = $2
|
61
|
+
when /^find_by_([_a-zA-Z]\w*)\!$/
|
62
|
+
@bang = true
|
63
|
+
names = $1
|
64
|
+
when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
|
65
|
+
@instantiator = $1 == 'initialize' ? :new : :create
|
66
|
+
names = $2
|
67
|
+
else
|
68
|
+
@finder = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
@attributes = names && names.split('_and_')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module EmbeddedDocument
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
|
8
|
+
extend Associations::ClassMethods
|
9
|
+
include Associations::InstanceMethods
|
10
|
+
|
11
|
+
include RailsCompatibility::EmbeddedDocument
|
12
|
+
include Validatable
|
13
|
+
include Serialization
|
14
|
+
|
15
|
+
extend Validations::Macros
|
16
|
+
|
17
|
+
key :_id, ObjectId
|
18
|
+
attr_accessor :_root_document
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def logger
|
24
|
+
MongoMapper.logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def inherited(subclass)
|
28
|
+
unless subclass.embeddable?
|
29
|
+
subclass.set_collection_name(collection_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
(@subclasses ||= []) << subclass
|
33
|
+
end
|
34
|
+
|
35
|
+
def subclasses
|
36
|
+
@subclasses
|
37
|
+
end
|
38
|
+
|
39
|
+
def keys
|
40
|
+
@keys ||= if parent = parent_model
|
41
|
+
parent.keys.dup
|
42
|
+
else
|
43
|
+
HashWithIndifferentAccess.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def key(*args)
|
48
|
+
key = Key.new(*args)
|
49
|
+
keys[key.name] = key
|
50
|
+
|
51
|
+
create_accessors_for(key)
|
52
|
+
create_key_in_subclasses(*args)
|
53
|
+
create_validations_for(key)
|
54
|
+
|
55
|
+
key
|
56
|
+
end
|
57
|
+
|
58
|
+
def using_object_id?
|
59
|
+
object_id_key?(:_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def object_id_key?(name)
|
63
|
+
key = keys[name.to_s]
|
64
|
+
key && key.type == ObjectId
|
65
|
+
end
|
66
|
+
|
67
|
+
def embeddable?
|
68
|
+
!self.ancestors.include?(Document)
|
69
|
+
end
|
70
|
+
|
71
|
+
def parent_model
|
72
|
+
(ancestors - [self,EmbeddedDocument]).find do |parent_class|
|
73
|
+
parent_class.ancestors.include?(EmbeddedDocument)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_mongo(instance)
|
78
|
+
return nil if instance.nil?
|
79
|
+
instance.to_mongo
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_mongo(value)
|
83
|
+
return nil if value.nil?
|
84
|
+
value.is_a?(self) ? value : initialize_doc(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def initialize_doc(doc)
|
89
|
+
begin
|
90
|
+
klass = doc['_type'].present? ? doc['_type'].constantize : self
|
91
|
+
klass.new(doc)
|
92
|
+
rescue NameError
|
93
|
+
new(doc)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def accessors_module
|
98
|
+
module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
|
99
|
+
const_defined?('MongoMapperKeys')
|
100
|
+
else
|
101
|
+
const_defined?('MongoMapperKeys', false)
|
102
|
+
end
|
103
|
+
|
104
|
+
if module_defined
|
105
|
+
const_get 'MongoMapperKeys'
|
106
|
+
else
|
107
|
+
const_set 'MongoMapperKeys', Module.new
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_accessors_for(key)
|
112
|
+
accessors_module.module_eval <<-end_eval
|
113
|
+
def #{key.name}
|
114
|
+
read_attribute(:'#{key.name}')
|
115
|
+
end
|
116
|
+
|
117
|
+
def #{key.name}_before_typecast
|
118
|
+
read_attribute_before_typecast(:'#{key.name}')
|
119
|
+
end
|
120
|
+
|
121
|
+
def #{key.name}=(value)
|
122
|
+
write_attribute(:'#{key.name}', value)
|
123
|
+
end
|
124
|
+
|
125
|
+
def #{key.name}?
|
126
|
+
read_attribute(:#{key.name}).present?
|
127
|
+
end
|
128
|
+
end_eval
|
129
|
+
include accessors_module
|
130
|
+
end
|
131
|
+
|
132
|
+
def create_key_in_subclasses(*args)
|
133
|
+
return if subclasses.blank?
|
134
|
+
|
135
|
+
subclasses.each do |subclass|
|
136
|
+
subclass.key(*args)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_validations_for(key)
|
141
|
+
attribute = key.name.to_sym
|
142
|
+
|
143
|
+
if key.options[:required]
|
144
|
+
validates_presence_of(attribute)
|
145
|
+
end
|
146
|
+
|
147
|
+
if key.options[:unique]
|
148
|
+
validates_uniqueness_of(attribute)
|
149
|
+
end
|
150
|
+
|
151
|
+
if key.options[:numeric]
|
152
|
+
number_options = key.type == Integer ? {:only_integer => true} : {}
|
153
|
+
validates_numericality_of(attribute, number_options)
|
154
|
+
end
|
155
|
+
|
156
|
+
if key.options[:format]
|
157
|
+
validates_format_of(attribute, :with => key.options[:format])
|
158
|
+
end
|
159
|
+
|
160
|
+
if key.options[:length]
|
161
|
+
length_options = case key.options[:length]
|
162
|
+
when Integer
|
163
|
+
{:minimum => 0, :maximum => key.options[:length]}
|
164
|
+
when Range
|
165
|
+
{:within => key.options[:length]}
|
166
|
+
when Hash
|
167
|
+
key.options[:length]
|
168
|
+
end
|
169
|
+
validates_length_of(attribute, length_options)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module InstanceMethods
|
175
|
+
def initialize(attrs={})
|
176
|
+
unless attrs.nil?
|
177
|
+
associations.each do |name, association|
|
178
|
+
if collection = attrs.delete(name)
|
179
|
+
if association.many? && association.klass.embeddable?
|
180
|
+
root_document = attrs[:_root_document] || self
|
181
|
+
collection.each do |doc|
|
182
|
+
doc[:_root_document] = root_document
|
183
|
+
end
|
184
|
+
end
|
185
|
+
send("#{association.name}=", collection)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
self.attributes = attrs
|
190
|
+
|
191
|
+
if respond_to?(:_type=) && self['_type'].blank?
|
192
|
+
self._type = self.class.name
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
if self.class.embeddable?
|
197
|
+
if read_attribute(:_id).blank?
|
198
|
+
write_attribute :_id, Mongo::ObjectID.new
|
199
|
+
@new_document = true
|
200
|
+
else
|
201
|
+
@new_document = false
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def new?
|
207
|
+
!!@new_document
|
208
|
+
end
|
209
|
+
|
210
|
+
def to_param
|
211
|
+
id.to_s
|
212
|
+
end
|
213
|
+
|
214
|
+
def attributes=(attrs)
|
215
|
+
return if attrs.blank?
|
216
|
+
attrs.each_pair do |name, value|
|
217
|
+
writer_method = "#{name}="
|
218
|
+
|
219
|
+
if respond_to?(writer_method)
|
220
|
+
self.send(writer_method, value)
|
221
|
+
else
|
222
|
+
self[name.to_s] = value
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def attributes
|
228
|
+
attrs = HashWithIndifferentAccess.new
|
229
|
+
|
230
|
+
embedded_keys.each do |key|
|
231
|
+
attrs[key.name] = read_attribute(key.name).try(:attributes)
|
232
|
+
end
|
233
|
+
|
234
|
+
non_embedded_keys.each do |key|
|
235
|
+
attrs[key.name] = read_attribute(key.name)
|
236
|
+
end
|
237
|
+
|
238
|
+
embedded_associations.each do |association|
|
239
|
+
documents = instance_variable_get(association.ivar)
|
240
|
+
next if documents.nil?
|
241
|
+
attrs[association.name] = documents.collect { |doc| doc.attributes }
|
242
|
+
end
|
243
|
+
|
244
|
+
attrs
|
245
|
+
end
|
246
|
+
|
247
|
+
def to_mongo
|
248
|
+
attrs = HashWithIndifferentAccess.new
|
249
|
+
|
250
|
+
_keys.each_pair do |name, key|
|
251
|
+
value = key.set(read_attribute(key.name))
|
252
|
+
attrs[name] = value unless value.nil?
|
253
|
+
end
|
254
|
+
|
255
|
+
embedded_associations.each do |association|
|
256
|
+
if documents = instance_variable_get(association.ivar)
|
257
|
+
attrs[association.name] = documents.map { |document| document.to_mongo }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
attrs
|
262
|
+
end
|
263
|
+
|
264
|
+
def clone
|
265
|
+
clone_attributes = self.attributes
|
266
|
+
clone_attributes.delete("_id")
|
267
|
+
self.class.new(clone_attributes)
|
268
|
+
end
|
269
|
+
|
270
|
+
def [](name)
|
271
|
+
read_attribute(name)
|
272
|
+
end
|
273
|
+
|
274
|
+
def []=(name, value)
|
275
|
+
ensure_key_exists(name)
|
276
|
+
write_attribute(name, value)
|
277
|
+
end
|
278
|
+
|
279
|
+
def ==(other)
|
280
|
+
other.is_a?(self.class) && _id == other._id
|
281
|
+
end
|
282
|
+
|
283
|
+
def id
|
284
|
+
self[:_id]
|
285
|
+
end
|
286
|
+
|
287
|
+
def id=(value)
|
288
|
+
if self.class.using_object_id?
|
289
|
+
value = MongoMapper.normalize_object_id(value)
|
290
|
+
else
|
291
|
+
@using_custom_id = true
|
292
|
+
end
|
293
|
+
|
294
|
+
self[:_id] = value
|
295
|
+
end
|
296
|
+
|
297
|
+
def using_custom_id?
|
298
|
+
!!@using_custom_id
|
299
|
+
end
|
300
|
+
|
301
|
+
def inspect
|
302
|
+
attributes_as_nice_string = key_names.collect do |name|
|
303
|
+
"#{name}: #{read_attribute(name).inspect}"
|
304
|
+
end.join(", ")
|
305
|
+
"#<#{self.class} #{attributes_as_nice_string}>"
|
306
|
+
end
|
307
|
+
|
308
|
+
def save(options={})
|
309
|
+
_root_document.try(:save, options)
|
310
|
+
end
|
311
|
+
|
312
|
+
def save!(options={})
|
313
|
+
_root_document.try(:save!, options)
|
314
|
+
end
|
315
|
+
|
316
|
+
def update_attributes(attrs={})
|
317
|
+
self.attributes = attrs
|
318
|
+
save
|
319
|
+
end
|
320
|
+
|
321
|
+
def update_attributes!(attrs={})
|
322
|
+
self.attributes = attrs
|
323
|
+
save!
|
324
|
+
end
|
325
|
+
|
326
|
+
def logger
|
327
|
+
self.class.logger
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
def _keys
|
332
|
+
self.metaclass.keys
|
333
|
+
end
|
334
|
+
|
335
|
+
def key_names
|
336
|
+
_keys.keys
|
337
|
+
end
|
338
|
+
|
339
|
+
def non_embedded_keys
|
340
|
+
_keys.values.select { |key| !key.embeddable? }
|
341
|
+
end
|
342
|
+
|
343
|
+
def embedded_keys
|
344
|
+
_keys.values.select { |key| key.embeddable? }
|
345
|
+
end
|
346
|
+
|
347
|
+
def ensure_key_exists(name)
|
348
|
+
self.metaclass.key(name) unless respond_to?("#{name}=")
|
349
|
+
end
|
350
|
+
|
351
|
+
def read_attribute(name)
|
352
|
+
if key = _keys[name]
|
353
|
+
value = key.get(instance_variable_get("@#{name}"))
|
354
|
+
instance_variable_set "@#{name}", value
|
355
|
+
value
|
356
|
+
else
|
357
|
+
raise KeyNotFound, "Could not find key: #{name.inspect}"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def read_attribute_before_typecast(name)
|
362
|
+
instance_variable_get("@#{name}_before_typecast")
|
363
|
+
end
|
364
|
+
|
365
|
+
def write_attribute(name, value)
|
366
|
+
key = _keys[name]
|
367
|
+
instance_variable_set "@#{name}_before_typecast", value
|
368
|
+
instance_variable_set "@#{name}", key.set(value)
|
369
|
+
end
|
370
|
+
|
371
|
+
def embedded_associations
|
372
|
+
associations.select do |name, association|
|
373
|
+
association.embeddable?
|
374
|
+
end.map do |name, association|
|
375
|
+
association
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end # InstanceMethods
|
379
|
+
end # EmbeddedDocument
|
380
|
+
end # MongoMapper
|