tpitale-mongo_mapper 0.6.9
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 +53 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper/associations/base.rb +110 -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/proxy.rb +113 -0
- data/lib/mongo_mapper/associations.rb +70 -0
- data/lib/mongo_mapper/callbacks.rb +109 -0
- data/lib/mongo_mapper/dirty.rb +136 -0
- data/lib/mongo_mapper/document.rb +472 -0
- data/lib/mongo_mapper/dynamic_finder.rb +74 -0
- data/lib/mongo_mapper/embedded_document.rb +384 -0
- data/lib/mongo_mapper/finder_options.rb +133 -0
- data/lib/mongo_mapper/key.rb +36 -0
- data/lib/mongo_mapper/observing.rb +50 -0
- data/lib/mongo_mapper/pagination.rb +55 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
- data/lib/mongo_mapper/serialization.rb +54 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
- data/lib/mongo_mapper/support.rb +206 -0
- data/lib/mongo_mapper/validations.rb +41 -0
- data/lib/mongo_mapper.rb +120 -0
- data/mongo_mapper.gemspec +173 -0
- data/specs.watchr +32 -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 +48 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
- data/test/functional/associations/test_many_documents_proxy.rb +387 -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/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +18 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_dirty.rb +159 -0
- data/test/functional/test_document.rb +1235 -0
- data/test/functional/test_embedded_document.rb +135 -0
- data/test/functional/test_logger.rb +20 -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 +378 -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 +166 -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 +204 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +718 -0
- data/test/unit/test_finder_options.rb +296 -0
- data/test/unit/test_key.rb +172 -0
- data/test/unit/test_mongo_mapper.rb +65 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +49 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +342 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +235 -0
@@ -0,0 +1,384 @@
|
|
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
|
+
read_attribute(:_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
|
+
write_attribute :_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
|
309
|
+
if _root_document
|
310
|
+
_root_document.save
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def save!
|
315
|
+
if _root_document
|
316
|
+
_root_document.save!
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def update_attributes(attrs={})
|
321
|
+
self.attributes = attrs
|
322
|
+
save
|
323
|
+
end
|
324
|
+
|
325
|
+
def update_attributes!(attrs={})
|
326
|
+
self.attributes = attrs
|
327
|
+
save!
|
328
|
+
end
|
329
|
+
|
330
|
+
def logger
|
331
|
+
self.class.logger
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
def _keys
|
336
|
+
self.metaclass.keys
|
337
|
+
end
|
338
|
+
|
339
|
+
def key_names
|
340
|
+
_keys.keys
|
341
|
+
end
|
342
|
+
|
343
|
+
def non_embedded_keys
|
344
|
+
_keys.values.select { |key| !key.embeddable? }
|
345
|
+
end
|
346
|
+
|
347
|
+
def embedded_keys
|
348
|
+
_keys.values.select { |key| key.embeddable? }
|
349
|
+
end
|
350
|
+
|
351
|
+
def ensure_key_exists(name)
|
352
|
+
self.metaclass.key(name) unless respond_to?("#{name}=")
|
353
|
+
end
|
354
|
+
|
355
|
+
def read_attribute(name)
|
356
|
+
if key = _keys[name]
|
357
|
+
value = key.get(instance_variable_get("@#{name}"))
|
358
|
+
instance_variable_set "@#{name}", value if !frozen?
|
359
|
+
value
|
360
|
+
else
|
361
|
+
raise KeyNotFound, "Could not find key: #{name.inspect}"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def read_attribute_before_typecast(name)
|
366
|
+
instance_variable_get("@#{name}_before_typecast")
|
367
|
+
end
|
368
|
+
|
369
|
+
def write_attribute(name, value)
|
370
|
+
key = _keys[name]
|
371
|
+
instance_variable_set "@#{name}_before_typecast", value
|
372
|
+
instance_variable_set "@#{name}", key.set(value)
|
373
|
+
end
|
374
|
+
|
375
|
+
def embedded_associations
|
376
|
+
associations.select do |name, association|
|
377
|
+
association.embeddable?
|
378
|
+
end.map do |name, association|
|
379
|
+
association
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end # InstanceMethods
|
383
|
+
end # EmbeddedDocument
|
384
|
+
end # MongoMapper
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
# Controls the parsing and handling of options used by finders.
|
3
|
+
#
|
4
|
+
# == Important Note
|
5
|
+
#
|
6
|
+
# This class is private to MongoMapper and should not be considered part of
|
7
|
+
# MongoMapper's public API. Some documentation herein, however, may prove
|
8
|
+
# useful for understanding how MongoMapper handles the parsing of finder
|
9
|
+
# conditions and options.
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
class FinderOptions
|
13
|
+
OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
|
14
|
+
|
15
|
+
def initialize(model, options)
|
16
|
+
raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
|
17
|
+
options = options.symbolize_keys
|
18
|
+
|
19
|
+
@model = model
|
20
|
+
@options = {}
|
21
|
+
@conditions = options.delete(:conditions) || {}
|
22
|
+
|
23
|
+
options.each_pair do |key, value|
|
24
|
+
if OptionKeys.include?(key)
|
25
|
+
@options[key] = value
|
26
|
+
else
|
27
|
+
@conditions[key] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
add_sci_scope
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Hash] Mongo compatible criteria options
|
35
|
+
#
|
36
|
+
# @see FinderOptions#to_mongo_criteria
|
37
|
+
def criteria
|
38
|
+
to_mongo_criteria(@conditions)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Hash] Mongo compatible options
|
42
|
+
def options
|
43
|
+
fields = @options.delete(:fields) || @options.delete(:select)
|
44
|
+
skip = @options.delete(:skip) || @options.delete(:offset) || 0
|
45
|
+
limit = @options.delete(:limit) || 0
|
46
|
+
sort = @options.delete(:sort) || convert_order_to_sort(@options.delete(:order))
|
47
|
+
|
48
|
+
{:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Array<Hash>] Mongo criteria and options enclosed in an Array
|
52
|
+
def to_a
|
53
|
+
[criteria, options]
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def to_mongo_criteria(conditions, parent_key=nil)
|
58
|
+
criteria = {}
|
59
|
+
|
60
|
+
conditions.each_pair do |field, value|
|
61
|
+
field = normalized_field(field)
|
62
|
+
|
63
|
+
if @model.object_id_key?(field) && value.is_a?(String)
|
64
|
+
value = Mongo::ObjectID.from_string(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
if field.is_a?(FinderOperator)
|
68
|
+
criteria.merge!(field.to_criteria(value))
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
case value
|
73
|
+
when Array
|
74
|
+
criteria[field] = operator?(field) ? value : {'$in' => value}
|
75
|
+
when Hash
|
76
|
+
criteria[field] = to_mongo_criteria(value, field)
|
77
|
+
else
|
78
|
+
criteria[field] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
criteria
|
83
|
+
end
|
84
|
+
|
85
|
+
def operator?(field)
|
86
|
+
field.to_s =~ /^\$/
|
87
|
+
end
|
88
|
+
|
89
|
+
def normalized_field(field)
|
90
|
+
field.to_s == 'id' ? :_id : field
|
91
|
+
end
|
92
|
+
|
93
|
+
# adds _type single collection inheritance scope for models that need it
|
94
|
+
def add_sci_scope
|
95
|
+
if @model.single_collection_inherited?
|
96
|
+
@conditions[:_type] = @model.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_mongo_fields(fields)
|
101
|
+
return if fields.blank?
|
102
|
+
|
103
|
+
if fields.is_a?(String)
|
104
|
+
fields.split(',').map { |field| field.strip }
|
105
|
+
else
|
106
|
+
fields.flatten.compact
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def convert_order_to_sort(sort)
|
111
|
+
return if sort.blank?
|
112
|
+
pieces = sort.split(',')
|
113
|
+
pieces.map { |s| to_mongo_sort_piece(s) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_mongo_sort_piece(str)
|
117
|
+
field, direction = str.strip.split(' ')
|
118
|
+
direction ||= 'ASC'
|
119
|
+
direction = direction.upcase == 'ASC' ? 1 : -1
|
120
|
+
[field, direction]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class FinderOperator
|
125
|
+
def initialize(field, operator)
|
126
|
+
@field, @operator = field, operator
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_criteria(value)
|
130
|
+
{@field => {@operator => value}}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
class Key
|
3
|
+
attr_accessor :name, :type, :options, :default_value
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
options = args.extract_options!
|
7
|
+
@name, @type = args.shift.to_s, args.shift
|
8
|
+
self.options = (options || {}).symbolize_keys
|
9
|
+
self.default_value = self.options.delete(:default)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
@name == other.name && @type == other.type
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(value)
|
17
|
+
type.to_mongo(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def embeddable?
|
21
|
+
type.respond_to?(:embeddable?) && type.embeddable? ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def number?
|
25
|
+
[Integer, Float].include?(type)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(value)
|
29
|
+
if value.nil? && !default_value.nil?
|
30
|
+
return default_value
|
31
|
+
end
|
32
|
+
|
33
|
+
type.from_mongo(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'singleton'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module MongoMapper
|
6
|
+
module Observing #:nodoc:
|
7
|
+
def self.included(model)
|
8
|
+
model.class_eval do
|
9
|
+
extend Observable
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Observer
|
15
|
+
include Singleton
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def observe(*models)
|
19
|
+
models.flatten!
|
20
|
+
models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
|
21
|
+
define_method(:observed_classes) { Set.new(models) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def observed_class
|
25
|
+
if observed_class_name = name[/(.*)Observer/, 1]
|
26
|
+
observed_class_name.constantize
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
Set.new(observed_classes).each { |klass| add_observer! klass }
|
35
|
+
end
|
36
|
+
|
37
|
+
def update(observed_method, object) #:nodoc:
|
38
|
+
send(observed_method, object) if respond_to?(observed_method)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def observed_classes
|
43
|
+
Set.new([self.class.observed_class].compact.flatten)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_observer!(klass)
|
47
|
+
klass.add_observer(self)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Pagination
|
3
|
+
class PaginationProxy
|
4
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|proxy_|^object_id$)/ }
|
5
|
+
|
6
|
+
attr_accessor :subject
|
7
|
+
attr_reader :total_entries, :per_page, :current_page
|
8
|
+
alias limit per_page
|
9
|
+
|
10
|
+
def initialize(total_entries, current_page, per_page=nil)
|
11
|
+
@total_entries = total_entries.to_i
|
12
|
+
self.per_page = per_page
|
13
|
+
self.current_page = current_page
|
14
|
+
end
|
15
|
+
|
16
|
+
def total_pages
|
17
|
+
(total_entries / per_page.to_f).ceil
|
18
|
+
end
|
19
|
+
|
20
|
+
def out_of_bounds?
|
21
|
+
current_page > total_pages
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous_page
|
25
|
+
current_page > 1 ? (current_page - 1) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_page
|
29
|
+
current_page < total_pages ? (current_page + 1) : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip
|
33
|
+
(current_page - 1) * per_page
|
34
|
+
end
|
35
|
+
alias offset skip # for will paginate support
|
36
|
+
|
37
|
+
|
38
|
+
def method_missing(name, *args, &block)
|
39
|
+
@subject.send(name, *args, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def per_page=(value)
|
44
|
+
value = 25 if value.blank?
|
45
|
+
@per_page = value.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_page=(value)
|
49
|
+
value = value.to_i
|
50
|
+
value = 1 if value < 1
|
51
|
+
@current_page = value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module RailsCompatibility
|
3
|
+
module EmbeddedDocument
|
4
|
+
def self.included(model)
|
5
|
+
model.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
|
8
|
+
alias_method :new_record?, :new?
|
9
|
+
end
|
10
|
+
|
11
|
+
class << model
|
12
|
+
alias has_many many
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def column_names
|
18
|
+
keys.keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def human_name
|
22
|
+
self.name.demodulize.titleize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|