djsun-mongo_mapper 0.5.0.1
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 +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +87 -0
- data/VERSION +1 -0
- data/bin/mmconsole +55 -0
- data/lib/mongo_mapper/associations/base.rb +83 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
- data/lib/mongo_mapper/associations/proxy.rb +74 -0
- data/lib/mongo_mapper/associations.rb +86 -0
- data/lib/mongo_mapper/callbacks.rb +106 -0
- data/lib/mongo_mapper/document.rb +308 -0
- data/lib/mongo_mapper/dynamic_finder.rb +35 -0
- data/lib/mongo_mapper/embedded_document.rb +354 -0
- data/lib/mongo_mapper/finder_options.rb +94 -0
- data/lib/mongo_mapper/key.rb +32 -0
- data/lib/mongo_mapper/observing.rb +50 -0
- data/lib/mongo_mapper/pagination.rb +51 -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/save_with_validation.rb +19 -0
- data/lib/mongo_mapper/serialization.rb +55 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
- data/lib/mongo_mapper/support.rb +171 -0
- data/lib/mongo_mapper/validations.rb +69 -0
- data/lib/mongo_mapper.rb +95 -0
- data/mongo_mapper.gemspec +156 -0
- data/specs.watchr +32 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/custom_matchers.rb +48 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +49 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
- data/test/functional/associations/test_many_proxy.rb +331 -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_document.rb +964 -0
- data/test/functional/test_embedded_document.rb +97 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_pagination.rb +87 -0
- data/test/functional/test_rails_compatibility.rb +30 -0
- data/test/functional/test_validations.rb +279 -0
- data/test/models.rb +169 -0
- data/test/test_helper.rb +30 -0
- data/test/unit/serializers/test_json_serializer.rb +193 -0
- data/test/unit/test_association_base.rb +144 -0
- data/test/unit/test_document.rb +165 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +643 -0
- data/test/unit/test_finder_options.rb +193 -0
- data/test/unit/test_key.rb +175 -0
- data/test/unit/test_mongomapper.rb +28 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +109 -0
- data/test/unit/test_rails_compatibility.rb +39 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +272 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +207 -0
@@ -0,0 +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
|
+
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
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
class FinderOptions
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def self.to_mongo_criteria(conditions, parent_key=nil)
|
6
|
+
criteria = {}
|
7
|
+
conditions.each_pair do |field, value|
|
8
|
+
field = field_normalized(field)
|
9
|
+
case value
|
10
|
+
when Array
|
11
|
+
operator_present = field.to_s =~ /^\$/
|
12
|
+
criteria[field] = if operator_present
|
13
|
+
value
|
14
|
+
else
|
15
|
+
{'$in' => value}
|
16
|
+
end
|
17
|
+
when Hash
|
18
|
+
criteria[field] = to_mongo_criteria(value, field)
|
19
|
+
else
|
20
|
+
criteria[field] = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
criteria
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.to_mongo_options(options)
|
28
|
+
options = options.dup
|
29
|
+
{
|
30
|
+
:fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
|
31
|
+
:skip => (options.delete(:skip) || options.delete(:offset) || 0).to_i,
|
32
|
+
:limit => (options.delete(:limit) || 0).to_i,
|
33
|
+
:sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.field_normalized(field)
|
38
|
+
if field.to_s == 'id'
|
39
|
+
:_id
|
40
|
+
else
|
41
|
+
field
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(options)
|
46
|
+
raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
|
47
|
+
@options = options.symbolize_keys
|
48
|
+
@conditions = @options.delete(:conditions) || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def criteria
|
52
|
+
self.class.to_mongo_criteria(@conditions)
|
53
|
+
end
|
54
|
+
|
55
|
+
def options
|
56
|
+
self.class.to_mongo_options(@options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_a
|
60
|
+
[criteria, options]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def self.to_mongo_fields(fields)
|
65
|
+
return if fields.blank?
|
66
|
+
|
67
|
+
if fields.is_a?(String)
|
68
|
+
fields.split(',').map { |field| field.strip }
|
69
|
+
else
|
70
|
+
fields.flatten.compact
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.to_mongo_sort(sort)
|
75
|
+
return if sort.blank?
|
76
|
+
pieces = sort.split(',')
|
77
|
+
pairs = pieces.map { |s| to_mongo_sort_piece(s) }
|
78
|
+
|
79
|
+
hash = OrderedHash.new
|
80
|
+
pairs.each do |pair|
|
81
|
+
field, sort_direction = pair
|
82
|
+
hash[field] = sort_direction
|
83
|
+
end
|
84
|
+
hash.symbolize_keys
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.to_mongo_sort_piece(str)
|
88
|
+
field, direction = str.strip.split(' ')
|
89
|
+
direction ||= 'ASC'
|
90
|
+
direction = direction.upcase == 'ASC' ? 1 : -1
|
91
|
+
[field, direction]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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 get(value)
|
25
|
+
if value.nil? && !default_value.nil?
|
26
|
+
return default_value
|
27
|
+
end
|
28
|
+
|
29
|
+
type.from_mongo(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
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,51 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Pagination
|
3
|
+
class PaginationProxy < BasicObject
|
4
|
+
attr_accessor :subject
|
5
|
+
attr_reader :total_entries, :per_page, :current_page
|
6
|
+
alias limit per_page
|
7
|
+
|
8
|
+
def initialize(total_entries, current_page, per_page=nil)
|
9
|
+
@total_entries = total_entries.to_i
|
10
|
+
self.per_page = per_page
|
11
|
+
self.current_page = current_page
|
12
|
+
end
|
13
|
+
|
14
|
+
def total_pages
|
15
|
+
(total_entries / per_page.to_f).ceil
|
16
|
+
end
|
17
|
+
|
18
|
+
def out_of_bounds?
|
19
|
+
current_page > total_pages
|
20
|
+
end
|
21
|
+
|
22
|
+
def previous_page
|
23
|
+
current_page > 1 ? (current_page - 1) : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_page
|
27
|
+
current_page < total_pages ? (current_page + 1) : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def skip
|
31
|
+
(current_page - 1) * per_page
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(name, *args, &block)
|
35
|
+
@subject.send(name, *args, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def per_page=(value)
|
40
|
+
value = 25 if value.blank?
|
41
|
+
@per_page = value.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_page=(value)
|
45
|
+
value = value.to_i
|
46
|
+
value = 1 if value < 1
|
47
|
+
@current_page = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
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
|
+
end
|
21
|
+
|
22
|
+
def to_param
|
23
|
+
raise "Missing to_param method in #{self.class}. You should implement it to return the unique identifier of this embedded document within a document."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module SaveWithValidation
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method_chain :save, :validation
|
6
|
+
alias_method_chain :save!, :validation
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def save_with_validation
|
12
|
+
valid? ? save_without_validation : false
|
13
|
+
end
|
14
|
+
|
15
|
+
def save_with_validation!
|
16
|
+
valid? ? save_without_validation! : raise(DocumentNotValid.new(self))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
|
3
|
+
module MongoMapper #:nodoc:
|
4
|
+
module Serialization
|
5
|
+
class Serializer #:nodoc:
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(record, options = {})
|
9
|
+
@record, @options = record, options.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def serializable_key_names
|
13
|
+
key_names = @record.attributes.keys
|
14
|
+
|
15
|
+
if options[:only]
|
16
|
+
options.delete(:except)
|
17
|
+
key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
|
18
|
+
else
|
19
|
+
options[:except] = Array(options[:except])
|
20
|
+
key_names = key_names - options[:except].collect { |n| n.to_s }
|
21
|
+
end
|
22
|
+
|
23
|
+
key_names
|
24
|
+
end
|
25
|
+
|
26
|
+
def serializable_method_names
|
27
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
28
|
+
method_attributes << name if @record.respond_to?(name.to_s)
|
29
|
+
method_attributes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serializable_names
|
34
|
+
serializable_key_names + serializable_method_names
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializable_record
|
38
|
+
returning(serializable_record = {}) do
|
39
|
+
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize
|
44
|
+
# overwrite to implement
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s(&block)
|
48
|
+
serialize(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
dir = Pathname(__FILE__).dirname.expand_path + 'serializers'
|
55
|
+
require dir + 'json_serializer'
|