mongo_mapper-unstable 2010.1.6 → 2010.1.12
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/VERSION +1 -1
- data/lib/mongo_mapper/descendant_appends.rb +44 -0
- data/lib/mongo_mapper/document.rb +54 -98
- data/lib/mongo_mapper/embedded_document.rb +28 -348
- data/lib/mongo_mapper/finder_options.rb +15 -33
- data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
- data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
- data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
- data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
- data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
- data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
- data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
- data/lib/mongo_mapper/plugins/associations.rb +104 -0
- data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
- data/lib/mongo_mapper/plugins/clone.rb +13 -0
- data/lib/mongo_mapper/plugins/descendants.rb +16 -0
- data/lib/mongo_mapper/plugins/dirty.rb +119 -0
- data/lib/mongo_mapper/plugins/equality.rb +11 -0
- data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
- data/lib/mongo_mapper/plugins/inspect.rb +14 -0
- data/lib/mongo_mapper/plugins/keys.rb +295 -0
- data/lib/mongo_mapper/plugins/logger.rb +17 -0
- data/lib/mongo_mapper/plugins/pagination.rb +85 -0
- data/lib/mongo_mapper/plugins/rails.rb +45 -0
- data/lib/mongo_mapper/plugins/serialization.rb +109 -0
- data/lib/mongo_mapper/plugins/validations.rb +48 -0
- data/lib/mongo_mapper/plugins.rb +19 -0
- data/lib/mongo_mapper/support.rb +36 -15
- data/lib/mongo_mapper.rb +23 -22
- data/performance/read_write.rb +52 -0
- data/specs.watchr +23 -2
- data/test/functional/associations/test_belongs_to_proxy.rb +1 -1
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +58 -39
- data/test/functional/associations/test_many_embedded_proxy.rb +103 -69
- data/test/functional/test_dirty.rb +1 -1
- data/test/functional/test_document.rb +25 -25
- data/test/functional/test_embedded_document.rb +66 -63
- data/test/functional/test_identity_map.rb +233 -0
- data/test/functional/test_modifiers.rb +14 -0
- data/test/functional/test_string_id_compatibility.rb +4 -4
- data/test/functional/test_validations.rb +13 -0
- data/test/models.rb +0 -39
- data/test/test_helper.rb +8 -2
- data/test/unit/associations/test_base.rb +1 -1
- data/test/unit/associations/test_proxy.rb +3 -3
- data/test/unit/test_descendant_appends.rb +71 -0
- data/test/unit/test_document.rb +35 -46
- data/test/unit/test_embedded_document.rb +218 -271
- data/test/unit/{test_key.rb → test_keys.rb} +0 -0
- data/test/unit/test_pagination.rb +10 -2
- data/test/unit/test_plugins.rb +42 -0
- data/test/unit/test_rails.rb +123 -0
- data/test/unit/{test_serializations.rb → test_serialization.rb} +0 -0
- data/test/unit/test_support.rb +10 -6
- data/test/unit/test_time_zones.rb +2 -2
- metadata +44 -31
- data/lib/mongo_mapper/associations/base.rb +0 -119
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +0 -26
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +0 -21
- data/lib/mongo_mapper/associations/collection.rb +0 -19
- data/lib/mongo_mapper/associations/in_array_proxy.rb +0 -137
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -26
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +0 -115
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +0 -31
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +0 -54
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +0 -11
- data/lib/mongo_mapper/associations/one_proxy.rb +0 -64
- data/lib/mongo_mapper/associations/proxy.rb +0 -116
- data/lib/mongo_mapper/associations.rb +0 -78
- data/lib/mongo_mapper/callbacks.rb +0 -61
- data/lib/mongo_mapper/dirty.rb +0 -117
- data/lib/mongo_mapper/key.rb +0 -36
- data/lib/mongo_mapper/mongo_mapper.rb +0 -125
- data/lib/mongo_mapper/pagination.rb +0 -66
- data/lib/mongo_mapper/rails_compatibility/document.rb +0 -15
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +0 -28
- data/lib/mongo_mapper/serialization.rb +0 -54
- data/lib/mongo_mapper/serializers/json_serializer.rb +0 -48
- data/lib/mongo_mapper/validations.rb +0 -39
- data/test/functional/test_rails_compatibility.rb +0 -25
@@ -0,0 +1,295 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module Keys
|
4
|
+
module ClassMethods
|
5
|
+
def inherited(descendant)
|
6
|
+
descendant.instance_variable_set(:@keys, keys.dup)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def keys
|
11
|
+
@keys ||= HashWithIndifferentAccess.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def key(*args)
|
15
|
+
key = Key.new(*args)
|
16
|
+
keys[key.name] = key
|
17
|
+
|
18
|
+
create_accessors_for(key)
|
19
|
+
create_key_in_descendants(*args)
|
20
|
+
create_indexes_for(key)
|
21
|
+
create_validations_for(key)
|
22
|
+
|
23
|
+
key
|
24
|
+
end
|
25
|
+
|
26
|
+
def using_object_id?
|
27
|
+
object_id_key?(:_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def object_id_key?(name)
|
31
|
+
key = keys[name.to_s]
|
32
|
+
key && key.type == ObjectId
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_mongo(instance)
|
36
|
+
return nil if instance.nil?
|
37
|
+
instance.to_mongo
|
38
|
+
end
|
39
|
+
|
40
|
+
def from_mongo(value)
|
41
|
+
return nil if value.nil?
|
42
|
+
value.is_a?(self) ? value : load(value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def load(attrs)
|
46
|
+
begin
|
47
|
+
klass = attrs['_type'].present? ? attrs['_type'].constantize : self
|
48
|
+
doc = klass.new(attrs)
|
49
|
+
doc.instance_variable_set("@new", false)
|
50
|
+
doc
|
51
|
+
rescue NameError
|
52
|
+
doc = new(attrs)
|
53
|
+
doc.instance_variable_set("@new", false)
|
54
|
+
doc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def accessors_module
|
60
|
+
module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
|
61
|
+
const_defined?('MongoMapperKeys')
|
62
|
+
else
|
63
|
+
const_defined?('MongoMapperKeys', false)
|
64
|
+
end
|
65
|
+
|
66
|
+
if module_defined
|
67
|
+
const_get 'MongoMapperKeys'
|
68
|
+
else
|
69
|
+
const_set 'MongoMapperKeys', Module.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_accessors_for(key)
|
74
|
+
accessors_module.module_eval <<-end_eval
|
75
|
+
def #{key.name}
|
76
|
+
read_key(:#{key.name})
|
77
|
+
end
|
78
|
+
|
79
|
+
def #{key.name}_before_typecast
|
80
|
+
read_key_before_typecast(:#{key.name})
|
81
|
+
end
|
82
|
+
|
83
|
+
def #{key.name}=(value)
|
84
|
+
write_key(:#{key.name}, value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def #{key.name}?
|
88
|
+
read_key(:#{key.name}).present?
|
89
|
+
end
|
90
|
+
end_eval
|
91
|
+
|
92
|
+
include accessors_module
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_key_in_descendants(*args)
|
96
|
+
return if descendants.blank?
|
97
|
+
descendants.each { |descendant| descendant.key(*args) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_indexes_for(key)
|
101
|
+
ensure_index key.name if key.options[:index] && !key.embeddable?
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_validations_for(key)
|
105
|
+
attribute = key.name.to_sym
|
106
|
+
|
107
|
+
if key.options[:required]
|
108
|
+
validates_presence_of(attribute)
|
109
|
+
end
|
110
|
+
|
111
|
+
if key.options[:unique]
|
112
|
+
validates_uniqueness_of(attribute)
|
113
|
+
end
|
114
|
+
|
115
|
+
if key.options[:numeric]
|
116
|
+
number_options = key.type == Integer ? {:only_integer => true} : {}
|
117
|
+
validates_numericality_of(attribute, number_options)
|
118
|
+
end
|
119
|
+
|
120
|
+
if key.options[:format]
|
121
|
+
validates_format_of(attribute, :with => key.options[:format])
|
122
|
+
end
|
123
|
+
|
124
|
+
if key.options[:length]
|
125
|
+
length_options = case key.options[:length]
|
126
|
+
when Integer
|
127
|
+
{:minimum => 0, :maximum => key.options[:length]}
|
128
|
+
when Range
|
129
|
+
{:within => key.options[:length]}
|
130
|
+
when Hash
|
131
|
+
key.options[:length]
|
132
|
+
end
|
133
|
+
validates_length_of(attribute, length_options)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module InstanceMethods
|
139
|
+
def self.included(model)
|
140
|
+
model.key :_id, ObjectId
|
141
|
+
end
|
142
|
+
|
143
|
+
def initialize(attrs={})
|
144
|
+
unless attrs.nil?
|
145
|
+
provided_keys = attrs.keys.map { |k| k.to_s }
|
146
|
+
unless provided_keys.include?('_id') || provided_keys.include?('id')
|
147
|
+
write_key :_id, Mongo::ObjectID.new
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
@new = true
|
152
|
+
self._type = self.class.name if respond_to?(:_type=)
|
153
|
+
self.attributes = attrs
|
154
|
+
end
|
155
|
+
|
156
|
+
def new?
|
157
|
+
@new
|
158
|
+
end
|
159
|
+
|
160
|
+
def attributes=(attrs)
|
161
|
+
return if attrs.blank?
|
162
|
+
|
163
|
+
attrs.each_pair do |name, value|
|
164
|
+
writer_method = "#{name}="
|
165
|
+
|
166
|
+
if respond_to?(writer_method)
|
167
|
+
self.send(writer_method, value)
|
168
|
+
else
|
169
|
+
self[name.to_s] = value
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def attributes
|
175
|
+
attrs = HashWithIndifferentAccess.new
|
176
|
+
|
177
|
+
keys.each_pair do |name, key|
|
178
|
+
value = key.set(self[key.name])
|
179
|
+
attrs[name] = value
|
180
|
+
end
|
181
|
+
|
182
|
+
embedded_associations.each do |association|
|
183
|
+
if documents = instance_variable_get(association.ivar)
|
184
|
+
attrs[association.name] = documents.map { |document| document.to_mongo }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
attrs
|
189
|
+
end
|
190
|
+
alias :to_mongo :attributes
|
191
|
+
|
192
|
+
def id
|
193
|
+
_id
|
194
|
+
end
|
195
|
+
|
196
|
+
def id=(value)
|
197
|
+
if self.class.using_object_id?
|
198
|
+
value = MongoMapper.normalize_object_id(value)
|
199
|
+
end
|
200
|
+
|
201
|
+
self[:_id] = value
|
202
|
+
end
|
203
|
+
|
204
|
+
def [](name)
|
205
|
+
read_key(name)
|
206
|
+
end
|
207
|
+
|
208
|
+
def []=(name, value)
|
209
|
+
ensure_key_exists(name)
|
210
|
+
write_key(name, value)
|
211
|
+
end
|
212
|
+
|
213
|
+
# @api public
|
214
|
+
def keys
|
215
|
+
self.class.keys
|
216
|
+
end
|
217
|
+
|
218
|
+
# @api private?
|
219
|
+
def key_names
|
220
|
+
keys.keys
|
221
|
+
end
|
222
|
+
|
223
|
+
# @api private?
|
224
|
+
def non_embedded_keys
|
225
|
+
keys.values.select { |key| !key.embeddable? }
|
226
|
+
end
|
227
|
+
|
228
|
+
# @api private?
|
229
|
+
def embedded_keys
|
230
|
+
keys.values.select { |key| key.embeddable? }
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
def ensure_key_exists(name)
|
235
|
+
self.class.key(name) unless respond_to?("#{name}=")
|
236
|
+
end
|
237
|
+
|
238
|
+
def read_key(name)
|
239
|
+
if key = keys[name]
|
240
|
+
var_name = "@#{name}"
|
241
|
+
value = key.get(instance_variable_get(var_name))
|
242
|
+
instance_variable_set(var_name, value)
|
243
|
+
else
|
244
|
+
raise KeyNotFound, "Could not find key: #{name.inspect}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def read_key_before_typecast(name)
|
249
|
+
instance_variable_get("@#{name}_before_typecast")
|
250
|
+
end
|
251
|
+
|
252
|
+
def write_key(name, value)
|
253
|
+
key = keys[name]
|
254
|
+
instance_variable_set "@#{name}_before_typecast", value
|
255
|
+
instance_variable_set "@#{name}", key.set(value)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class Key
|
260
|
+
attr_accessor :name, :type, :options, :default_value
|
261
|
+
|
262
|
+
def initialize(*args)
|
263
|
+
options = args.extract_options!
|
264
|
+
@name, @type = args.shift.to_s, args.shift
|
265
|
+
self.options = (options || {}).symbolize_keys
|
266
|
+
self.default_value = self.options.delete(:default)
|
267
|
+
end
|
268
|
+
|
269
|
+
def ==(other)
|
270
|
+
@name == other.name && @type == other.type
|
271
|
+
end
|
272
|
+
|
273
|
+
def embeddable?
|
274
|
+
type.respond_to?(:embeddable?) && type.embeddable? ? true : false
|
275
|
+
end
|
276
|
+
|
277
|
+
def number?
|
278
|
+
[Integer, Float].include?(type)
|
279
|
+
end
|
280
|
+
|
281
|
+
def get(value)
|
282
|
+
if value.nil? && !default_value.nil?
|
283
|
+
return default_value
|
284
|
+
end
|
285
|
+
|
286
|
+
type.from_mongo(value)
|
287
|
+
end
|
288
|
+
|
289
|
+
def set(value)
|
290
|
+
type.to_mongo(value)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module Pagination
|
4
|
+
module ClassMethods
|
5
|
+
def per_page
|
6
|
+
25
|
7
|
+
end
|
8
|
+
|
9
|
+
def paginate(options)
|
10
|
+
per_page = options.delete(:per_page) || self.per_page
|
11
|
+
page = options.delete(:page)
|
12
|
+
total_entries = count(options)
|
13
|
+
pagination = Pagination::PaginationProxy.new(total_entries, page, per_page)
|
14
|
+
|
15
|
+
options.merge!(:limit => pagination.limit, :skip => pagination.skip)
|
16
|
+
pagination.subject = find_every(options)
|
17
|
+
pagination
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class PaginationProxy
|
22
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
23
|
+
|
24
|
+
attr_accessor :subject
|
25
|
+
attr_reader :total_entries, :per_page, :current_page
|
26
|
+
alias limit per_page
|
27
|
+
|
28
|
+
def initialize(total_entries, current_page, per_page=nil)
|
29
|
+
@total_entries = total_entries.to_i
|
30
|
+
self.per_page = per_page
|
31
|
+
self.current_page = current_page
|
32
|
+
end
|
33
|
+
|
34
|
+
def total_pages
|
35
|
+
(total_entries / per_page.to_f).ceil
|
36
|
+
end
|
37
|
+
|
38
|
+
def out_of_bounds?
|
39
|
+
current_page > total_pages
|
40
|
+
end
|
41
|
+
|
42
|
+
def previous_page
|
43
|
+
current_page > 1 ? (current_page - 1) : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def next_page
|
47
|
+
current_page < total_pages ? (current_page + 1) : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def skip
|
51
|
+
(current_page - 1) * per_page
|
52
|
+
end
|
53
|
+
alias offset skip # for will paginate support
|
54
|
+
|
55
|
+
def send(method, *args, &block)
|
56
|
+
if respond_to?(method)
|
57
|
+
super
|
58
|
+
else
|
59
|
+
subject.send(method, *args, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def ===(other)
|
64
|
+
other === subject
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_missing(name, *args, &block)
|
68
|
+
@subject.send(name, *args, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def per_page=(value)
|
73
|
+
value = 25 if value.blank?
|
74
|
+
@per_page = value.to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
def current_page=(value)
|
78
|
+
value = value.to_i
|
79
|
+
value = 1 if value < 1
|
80
|
+
@current_page = value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module Rails
|
4
|
+
module InstanceMethods
|
5
|
+
def to_param
|
6
|
+
id.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def new_record?
|
10
|
+
new?
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_attribute(name)
|
14
|
+
self[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_attribute_before_typecast(name)
|
18
|
+
read_key_before_typecast(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_attribute(name, value)
|
22
|
+
self[name] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def has_one(*args)
|
28
|
+
one(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_many(*args)
|
32
|
+
many(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def column_names
|
36
|
+
keys.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def human_name
|
40
|
+
self.name.demodulize.titleize
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
|
3
|
+
module MongoMapper
|
4
|
+
module Plugins
|
5
|
+
module Serialization
|
6
|
+
class Serializer
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(record, options={})
|
10
|
+
@record, @options = record, options.dup
|
11
|
+
end
|
12
|
+
|
13
|
+
def serializable_key_names
|
14
|
+
key_names = @record.attributes.keys
|
15
|
+
|
16
|
+
if options[:only]
|
17
|
+
options.delete(:except)
|
18
|
+
key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
|
19
|
+
else
|
20
|
+
options[:except] = Array(options[:except])
|
21
|
+
key_names = key_names - options[:except].collect { |n| n.to_s }
|
22
|
+
end
|
23
|
+
|
24
|
+
key_names
|
25
|
+
end
|
26
|
+
|
27
|
+
def serializable_method_names
|
28
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
29
|
+
method_attributes << name if @record.respond_to?(name.to_s)
|
30
|
+
method_attributes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def serializable_names
|
35
|
+
serializable_key_names + serializable_method_names
|
36
|
+
end
|
37
|
+
|
38
|
+
def serializable_record
|
39
|
+
returning(serializable_record = {}) do
|
40
|
+
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def serialize
|
45
|
+
# overwrite to implement
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s(&block)
|
49
|
+
serialize(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module Json
|
54
|
+
def self.included(base)
|
55
|
+
base.cattr_accessor :include_root_in_json, :instance_writer => false
|
56
|
+
base.extend ClassMethods
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
def json_class_name
|
61
|
+
@json_class_name ||= name.demodulize.underscore.inspect
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_json(options={})
|
66
|
+
apply_to_json_defaults(options)
|
67
|
+
|
68
|
+
if include_root_in_json
|
69
|
+
"{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
|
70
|
+
else
|
71
|
+
JsonSerializer.new(self, options).to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def from_json(json)
|
76
|
+
self.attributes = ActiveSupport::JSON.decode(json)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
class JsonSerializer < Serializer
|
81
|
+
def serialize
|
82
|
+
serializable_record.to_json
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def apply_to_json_defaults(options)
|
88
|
+
unless options[:only]
|
89
|
+
methods = [options.delete(:methods)].flatten.compact
|
90
|
+
methods << :id
|
91
|
+
options[:methods] = methods.uniq
|
92
|
+
end
|
93
|
+
|
94
|
+
except = [options.delete(:except)].flatten.compact
|
95
|
+
except << :_id
|
96
|
+
options[:except] = except
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module InstanceMethods
|
101
|
+
def self.included(model)
|
102
|
+
model.class_eval do
|
103
|
+
include Json
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
module Validations
|
4
|
+
module InstanceMethods
|
5
|
+
def self.included(model)
|
6
|
+
model.class_eval { include Validatable }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module DocumentMacros
|
11
|
+
def validates_uniqueness_of(*args)
|
12
|
+
add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ValidatesUniquenessOf < Validatable::ValidationBase
|
17
|
+
option :scope, :case_sensitive
|
18
|
+
default :case_sensitive => true
|
19
|
+
|
20
|
+
def valid?(instance)
|
21
|
+
value = instance[attribute]
|
22
|
+
return true if allow_blank && value.blank?
|
23
|
+
return true if allow_nil && value.nil?
|
24
|
+
base_conditions = case_sensitive ? {self.attribute => value} : {}
|
25
|
+
doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
|
26
|
+
doc.nil? || instance._id == doc._id
|
27
|
+
end
|
28
|
+
|
29
|
+
def message(instance)
|
30
|
+
super || "has already been taken"
|
31
|
+
end
|
32
|
+
|
33
|
+
def scope_conditions(instance)
|
34
|
+
return {} unless scope
|
35
|
+
Array(scope).inject({}) do |conditions, key|
|
36
|
+
conditions.merge(key => instance[key])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def where_conditions(instance)
|
41
|
+
conditions = {}
|
42
|
+
conditions[attribute] = /#{instance[attribute].to_s}/i unless case_sensitive
|
43
|
+
conditions
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Plugins
|
3
|
+
def plugins
|
4
|
+
@plugins ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def plugin(mod)
|
8
|
+
if mod.const_defined?(:ClassMethods)
|
9
|
+
extend mod::ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
if mod.const_defined?(:InstanceMethods)
|
13
|
+
include mod::InstanceMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
plugins << mod
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/mongo_mapper/support.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
class Array
|
4
2
|
def self.to_mongo(value)
|
5
3
|
value = value.respond_to?(:lines) ? value.lines : value
|
@@ -26,11 +24,18 @@ class Binary
|
|
26
24
|
end
|
27
25
|
|
28
26
|
class Boolean
|
27
|
+
BOOLEAN_MAPPING = {
|
28
|
+
true => true, 'true' => true, 'TRUE' => true, 'True' => true, 't' => true, 'T' => true, '1' => true, 1 => true, 1.0 => true,
|
29
|
+
false => false, 'false' => false, 'FALSE' => false, 'False' => false, 'f' => false, 'F' => false, '0' => false, 0 => false, 0.0 => false, nil => false
|
30
|
+
}
|
31
|
+
|
29
32
|
def self.to_mongo(value)
|
30
33
|
if value.is_a?(Boolean)
|
31
34
|
value
|
32
35
|
else
|
33
|
-
|
36
|
+
v = BOOLEAN_MAPPING[value]
|
37
|
+
v = value.to_s.downcase == 'true' if v.nil? # Check all mixed case spellings for true
|
38
|
+
v
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
@@ -41,8 +46,12 @@ end
|
|
41
46
|
|
42
47
|
class Date
|
43
48
|
def self.to_mongo(value)
|
44
|
-
|
45
|
-
|
49
|
+
if value.nil? || value == ''
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
date = value.is_a?(Date) || value.is_a?(Time) ? value : Date.parse(value.to_s)
|
53
|
+
Time.utc(date.year, date.month, date.day)
|
54
|
+
end
|
46
55
|
rescue
|
47
56
|
nil
|
48
57
|
end
|
@@ -71,7 +80,7 @@ end
|
|
71
80
|
class Integer
|
72
81
|
def self.to_mongo(value)
|
73
82
|
value_to_i = value.to_i
|
74
|
-
if value_to_i == 0
|
83
|
+
if value_to_i == 0 && value != value_to_i
|
75
84
|
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
76
85
|
else
|
77
86
|
value_to_i
|
@@ -154,15 +163,26 @@ class String
|
|
154
163
|
end
|
155
164
|
end
|
156
165
|
|
157
|
-
class
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
166
|
+
class SymbolOperator
|
167
|
+
def initialize(field, operator, options={})
|
168
|
+
@field, @operator = field, operator
|
169
|
+
end unless method_defined?(:initialize)
|
170
|
+
|
171
|
+
def to_mm_criteria(value)
|
172
|
+
{MongoMapper::FinderOptions.normalized_field(@field) => {"$#{@operator}" => value}}
|
162
173
|
end
|
163
174
|
|
164
|
-
def
|
165
|
-
|
175
|
+
def to_mm_order
|
176
|
+
[@field.to_s, MongoMapper::FinderOptions.normalized_order_direction(@operator)]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Symbol
|
181
|
+
%w(gt lt gte lte ne in nin mod size where exists asc desc).each do |operator|
|
182
|
+
define_method(operator) do
|
183
|
+
SymbolOperator.new(self, operator)
|
184
|
+
end unless method_defined?(operator)
|
185
|
+
end
|
166
186
|
end
|
167
187
|
|
168
188
|
class Time
|
@@ -170,8 +190,9 @@ class Time
|
|
170
190
|
if value.nil? || value == ''
|
171
191
|
nil
|
172
192
|
else
|
173
|
-
time = MongoMapper.time_class.parse(value.to_s)
|
174
|
-
time
|
193
|
+
time = value.is_a?(Time) ? value : MongoMapper.time_class.parse(value.to_s)
|
194
|
+
# Convert time to milliseconds since BSON stores dates with that accurracy, but Ruby uses microseconds
|
195
|
+
Time.at((time.to_f * 1000).round / 1000.0).utc if time
|
175
196
|
end
|
176
197
|
end
|
177
198
|
|