mongodb 0.0.1 → 0.0.2
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/Rakefile +3 -5
- data/lib/mongodb/driver.rb +33 -0
- data/lib/mongodb/driver/collection.rb +156 -0
- data/lib/mongodb/driver/database.rb +6 -0
- data/lib/mongodb/driver/dynamic_finders.rb +41 -0
- data/lib/{mongo_db → mongodb}/driver/spec.rb +12 -12
- data/lib/mongodb/gems.rb +6 -0
- data/lib/mongodb/integration/locales.rb +4 -0
- data/lib/mongodb/integration/locales/activemodel/ru.yml +27 -0
- data/lib/mongodb/migration.rb +8 -0
- data/lib/mongodb/migration/definition.rb +19 -0
- data/lib/mongodb/migration/migration.rb +68 -0
- data/lib/mongodb/migration/tasks.rb +19 -0
- data/lib/mongodb/model.rb +26 -0
- data/lib/mongodb/model/assignment.rb +65 -0
- data/lib/mongodb/model/attribute_convertors.rb +54 -0
- data/lib/mongodb/model/callbacks.rb +36 -0
- data/lib/mongodb/model/crud.rb +57 -0
- data/lib/mongodb/model/db.rb +53 -0
- data/lib/mongodb/model/misc.rb +33 -0
- data/lib/mongodb/model/model.rb +11 -0
- data/lib/mongodb/model/query.rb +36 -0
- data/lib/mongodb/model/scope.rb +99 -0
- data/lib/mongodb/model/spec.rb +12 -0
- data/lib/mongodb/model/support/types.rb +110 -0
- data/lib/mongodb/model/validation.rb +5 -0
- data/lib/mongodb/object.rb +18 -0
- data/lib/mongodb/object/object_helper.rb +62 -0
- data/lib/mongodb/object/object_serializer.rb +273 -0
- data/readme.md +261 -6
- data/spec/driver/collection_spec.rb +83 -0
- data/spec/{mongo_model/hash → driver}/crud_spec.rb +30 -29
- data/spec/driver/database_spec.rb +9 -0
- data/spec/driver/dynamic_finders_spec.rb +50 -0
- data/spec/driver/fixes_spec.rb +12 -0
- data/spec/driver/hash_helper_spec.rb +24 -0
- data/spec/driver/spec_helper.rb +28 -0
- data/spec/integration/am_conversion_spec.rb +1 -0
- data/spec/integration/am_validation_spec.rb +34 -0
- data/spec/integration/validatable2_spec.rb +40 -0
- data/spec/migration/migration_spec.rb +60 -0
- data/spec/model/assignment_spec.rb +80 -0
- data/spec/model/attribute_convertors_spec.rb +73 -0
- data/spec/model/callbacks_spec.rb +47 -0
- data/spec/model/crud_spec.rb +151 -0
- data/spec/model/db_spec.rb +63 -0
- data/spec/model/misc_spec.rb +58 -0
- data/spec/model/query_spec.rb +47 -0
- data/spec/model/scope_spec.rb +149 -0
- data/spec/model/spec_helper.rb +4 -0
- data/spec/model/validation_spec.rb +37 -0
- data/spec/object/callbacks_spec.rb +97 -0
- data/spec/object/crud_shared.rb +53 -0
- data/spec/object/crud_spec.rb +55 -0
- data/spec/object/spec_helper.rb +14 -0
- data/spec/{mongo_model/object → object}/validation_spec.rb +38 -36
- metadata +92 -25
- data/lib/mongo_db.rb +0 -3
- data/lib/mongo_db/driver.rb +0 -5
- data/lib/mongo_db/driver/connection.rb +0 -0
- data/lib/mongo_db/driver/database.rb +0 -5
- data/lib/mongo_db/gems.rb +0 -2
- data/lib/mongo_db/model.rb +0 -0
- data/spec/mongo_ext/migration_spec.rb +0 -0
- data/spec/mongo_ext/misc_spec.rb +0 -10
- data/spec/mongo_ext/spec_helper.rb +0 -4
- data/spec/mongo_model/model/crud_spec.rb +0 -123
- data/spec/mongo_model/model/query_spec.rb +0 -0
- data/spec/mongo_model/object/callbacks_spec.rb +0 -100
- data/spec/mongo_model/object/crud_shared.rb +0 -53
- data/spec/mongo_model/object/crud_spec.rb +0 -45
- data/spec/mongo_model/spec_helper.rb +0 -1
- data/spec/query_spec.rb +0 -0
- data/spec/test_spec.rb +0 -5
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Boolean
|
3
|
+
#
|
4
|
+
module Mongo::Model::BooleanType
|
5
|
+
Mapping = {
|
6
|
+
true => true,
|
7
|
+
'true' => true,
|
8
|
+
'TRUE' => true,
|
9
|
+
'True' => true,
|
10
|
+
't' => true,
|
11
|
+
'T' => true,
|
12
|
+
'1' => true,
|
13
|
+
1 => true,
|
14
|
+
1.0 => true,
|
15
|
+
false => false,
|
16
|
+
'false' => false,
|
17
|
+
'FALSE' => false,
|
18
|
+
'False' => false,
|
19
|
+
'f' => false,
|
20
|
+
'F' => false,
|
21
|
+
'0' => false,
|
22
|
+
0 => false,
|
23
|
+
0.0 => false,
|
24
|
+
nil => nil
|
25
|
+
}
|
26
|
+
|
27
|
+
def cast value
|
28
|
+
if value.is_a? Boolean
|
29
|
+
value
|
30
|
+
else
|
31
|
+
Mapping[value] || false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Boolean; end unless defined?(Boolean)
|
37
|
+
|
38
|
+
Boolean.extend Mongo::Model::BooleanType
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# Date
|
43
|
+
#
|
44
|
+
require 'date'
|
45
|
+
Date.class_eval do
|
46
|
+
def self.cast value
|
47
|
+
if value.nil? || value == ''
|
48
|
+
nil
|
49
|
+
else
|
50
|
+
date = value.is_a?(::Date) || value.is_a?(::Time) ? value : ::Date.parse(value.to_s)
|
51
|
+
date.to_date
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# Float
|
61
|
+
#
|
62
|
+
Float.class_eval do
|
63
|
+
def self.cast value
|
64
|
+
value.nil? ? nil : value.to_f
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
#
|
70
|
+
# Integer
|
71
|
+
#
|
72
|
+
Integer.class_eval do
|
73
|
+
def self.cast value
|
74
|
+
value_to_i = value.to_i
|
75
|
+
if value_to_i == 0 && value != value_to_i
|
76
|
+
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
77
|
+
else
|
78
|
+
value_to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
#
|
85
|
+
# String
|
86
|
+
#
|
87
|
+
String.class_eval do
|
88
|
+
def self.cast value
|
89
|
+
value.nil? ? nil : value.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
#
|
95
|
+
# Time
|
96
|
+
#
|
97
|
+
Time.class_eval do
|
98
|
+
def self.cast value
|
99
|
+
if value.nil? || value == ''
|
100
|
+
nil
|
101
|
+
else
|
102
|
+
# time_class = ::Time.try(:zone).present? ? ::Time.zone : ::Time
|
103
|
+
# time = value.is_a?(::Time) ? value : time_class.parse(value.to_s)
|
104
|
+
# strip milliseconds as Ruby does micro and bson does milli and rounding rounded wrong
|
105
|
+
# at(time.to_i).utc if time
|
106
|
+
|
107
|
+
value.is_a?(::Time) ? value : Date.parse(value.to_s).to_time
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'mongodb/driver'
|
2
|
+
|
3
|
+
%w(
|
4
|
+
object_serializer
|
5
|
+
object_helper
|
6
|
+
).each{|f| require "mongodb/object/#{f}"}
|
7
|
+
|
8
|
+
Mongo.defaults[:callbacks] = true
|
9
|
+
|
10
|
+
# collection
|
11
|
+
Mongo::Collection.class_eval do
|
12
|
+
include Mongo::ObjectHelper
|
13
|
+
|
14
|
+
%w(insert update remove save).each do |method|
|
15
|
+
alias_method "#{method}_without_object", method
|
16
|
+
alias_method method, "#{method}_with_object"
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Mongo::ObjectHelper
|
2
|
+
#
|
3
|
+
# CRUD
|
4
|
+
#
|
5
|
+
def save_with_object doc, opts = {}
|
6
|
+
if doc.is_a? Hash
|
7
|
+
save_without_object doc, opts
|
8
|
+
else
|
9
|
+
::Mongo::ObjectSerializer.new(doc).save opts, self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def insert_with_object args, opts = {}
|
14
|
+
if args.is_a?(Hash) or args.is_a?(Array)
|
15
|
+
insert_without_object args, opts
|
16
|
+
else
|
17
|
+
::Mongo::ObjectSerializer.new(args).insert opts, self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_with_object selector, doc, opts = {}
|
22
|
+
if doc.is_a?(Hash)
|
23
|
+
update_without_object selector, doc, opts
|
24
|
+
else
|
25
|
+
raise "can't use update selector with object (#{selector}, {#{doc}})!" unless selector == nil
|
26
|
+
::Mongo::ObjectSerializer.new(doc).update opts, self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_with_object arg = {}, opts = {}
|
31
|
+
if arg.is_a? Hash
|
32
|
+
remove_without_object arg, opts
|
33
|
+
else
|
34
|
+
::Mongo::ObjectSerializer.new(arg).remove opts, self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def save! doc, opts = {}
|
39
|
+
save(doc, opts) || raise(Mongo::Error, "can't save #{doc.inspect}!")
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#
|
44
|
+
# Querying
|
45
|
+
#
|
46
|
+
def first selector = {}, opts = {}, &block
|
47
|
+
opts = opts.clone
|
48
|
+
object = (opts.delete(:object) == false) ? false : true
|
49
|
+
doc = super selector, opts, &block
|
50
|
+
object ? ::Mongo::ObjectSerializer.build(doc) : doc
|
51
|
+
end
|
52
|
+
|
53
|
+
def each selector = {}, opts = {}, &block
|
54
|
+
opts = opts.clone
|
55
|
+
object = (opts.delete(:object) == false) ? false : true
|
56
|
+
super selector, opts do |doc|
|
57
|
+
doc = ::Mongo::ObjectSerializer.build(doc) if object
|
58
|
+
block.call doc
|
59
|
+
end
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
class Mongo::ObjectSerializer
|
2
|
+
SIMPLE_TYPES = [
|
3
|
+
Fixnum, Float,
|
4
|
+
TrueClass, FalseClass,
|
5
|
+
String, Symbol,
|
6
|
+
Array, Hash, Set,
|
7
|
+
Data, DateTime,
|
8
|
+
NilClass, Time,
|
9
|
+
BSON::ObjectId
|
10
|
+
].to_set
|
11
|
+
|
12
|
+
attr_reader :object
|
13
|
+
|
14
|
+
def initialize object
|
15
|
+
@object = object
|
16
|
+
end
|
17
|
+
|
18
|
+
def save opts, collection
|
19
|
+
if _id
|
20
|
+
self.update opts.merge(upsert: true), collection
|
21
|
+
else
|
22
|
+
self.insert opts, collection
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def insert opts, collection
|
27
|
+
opts, validate, callbacks = parse_object_options opts
|
28
|
+
|
29
|
+
# before callbacks
|
30
|
+
return false if callbacks and !run_callbacks(objects, [:before, :validate], [:before, :save], [:before, :create])
|
31
|
+
|
32
|
+
# validation
|
33
|
+
return false if validate and !valid?
|
34
|
+
|
35
|
+
# saving document
|
36
|
+
doc = to_document
|
37
|
+
collection.insert_without_object doc, opts
|
38
|
+
id = doc[:_id] || doc['_id'] || raise("internal error: no id after document insertion (#{doc})!")
|
39
|
+
object.instance_variable_set :@_id, id
|
40
|
+
update_internal_state!
|
41
|
+
|
42
|
+
# after callbacks
|
43
|
+
run_callbacks(objects, [:after, :create], [:after, :save], [:after, :validate]) if callbacks
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def update opts, collection
|
49
|
+
opts, validate, callbacks = parse_object_options opts
|
50
|
+
|
51
|
+
# before callbacks.
|
52
|
+
# we need to sort out embedded objects into created, updated and destroyed
|
53
|
+
created_objects, updated_objects, destroyed_objects = [], [], []
|
54
|
+
if callbacks
|
55
|
+
original_ids = original_objects.collect{|obj| obj.object_id}.to_set
|
56
|
+
objects.each do |obj|
|
57
|
+
(original_ids.include?(obj.object_id) ? updated_objects : created_objects) << obj
|
58
|
+
end
|
59
|
+
|
60
|
+
objects_ids = objects.collect{|obj| obj.object_id}.to_set
|
61
|
+
destroyed_objects = original_objects.select{|obj| !objects_ids.include?(obj.object_id)}
|
62
|
+
|
63
|
+
all_successfull = [
|
64
|
+
run_callbacks(created_objects, [:before, :validate], [:before, :save], [:before, :create]),
|
65
|
+
run_callbacks(updated_objects, [:before, :validate], [:before, :save], [:before, :update]),
|
66
|
+
run_callbacks(destroyed_objects, [:before, :validate], [:before, :destroy])
|
67
|
+
].reduce(:&)
|
68
|
+
|
69
|
+
return false unless all_successfull
|
70
|
+
end
|
71
|
+
|
72
|
+
# validation
|
73
|
+
return false if validate and !valid?
|
74
|
+
|
75
|
+
# saving document
|
76
|
+
doc = to_document
|
77
|
+
id = _id || raise("can't update document without id (#{doc})!")
|
78
|
+
collection.update_without_object({_id: id}, doc, opts)
|
79
|
+
update_internal_state!
|
80
|
+
|
81
|
+
# after callbacks
|
82
|
+
if callbacks
|
83
|
+
run_callbacks(created_objects, [:after, :create], [:after, :save], [:after, :validate])
|
84
|
+
run_callbacks(updated_objects, [:after, :update], [:after, :save], [:after, :validate])
|
85
|
+
run_callbacks(destroyed_objects, [:after, :destroy], [:after, :validate])
|
86
|
+
end
|
87
|
+
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def remove opts, collection
|
92
|
+
opts, validate, callbacks = parse_object_options opts
|
93
|
+
|
94
|
+
# before callbacks
|
95
|
+
if callbacks
|
96
|
+
# we need to run :destroy callbacks also on detached embedded objects.
|
97
|
+
all_objects = (objects + original_objects).uniq{|o| o.object_id}
|
98
|
+
return false unless run_callbacks(all_objects, [:before, :validate], [:before, :destroy])
|
99
|
+
end
|
100
|
+
|
101
|
+
# validation
|
102
|
+
return false if validate and !valid?
|
103
|
+
|
104
|
+
# saving document
|
105
|
+
id = _id || "can't destroy object without _id (#{arg})!"
|
106
|
+
collection.remove_without_object({_id: id}, opts)
|
107
|
+
update_internal_state!
|
108
|
+
|
109
|
+
# after callbacks
|
110
|
+
run_callbacks(objects, [:after, :destroy], [:after, :validate]) if callbacks
|
111
|
+
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_document
|
116
|
+
_to_document object
|
117
|
+
end
|
118
|
+
|
119
|
+
def _id
|
120
|
+
object.instance_variable_get(:@_id)
|
121
|
+
end
|
122
|
+
|
123
|
+
def valid?
|
124
|
+
objects.each do |obj|
|
125
|
+
return false if obj.respond_to?(:_valid?) and !obj._valid?
|
126
|
+
end
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
def run_callbacks objects, *callbacks
|
131
|
+
callbacks.each do |type, method_name|
|
132
|
+
objects.each do |obj|
|
133
|
+
if obj.respond_to? :_run_callbacks
|
134
|
+
return false if obj._run_callbacks(type, method_name) == false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
141
|
+
def objects
|
142
|
+
@objects_cache ||= begin
|
143
|
+
objects = []
|
144
|
+
_each_object(object){|obj| objects << obj}
|
145
|
+
objects
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def update_internal_state!
|
150
|
+
self.original_objects = objects if Mongo.defaults[:callbacks]
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
def original_objects; object.instance_variable_get(:@_original_objects) end
|
155
|
+
def original_objects= objects; object.instance_variable_set(:@_original_objects, objects) end
|
156
|
+
|
157
|
+
def parse_object_options opts
|
158
|
+
opts = opts.clone
|
159
|
+
validate = opts.delete(:validate) == false ? false : true
|
160
|
+
callbacks = opts.delete(:callbacks) == false ? false : Mongo.defaults[:callbacks]
|
161
|
+
return opts, validate, callbacks
|
162
|
+
end
|
163
|
+
|
164
|
+
# need this to allow change it in specs
|
165
|
+
# RSpec adds @mock_proxy, and we need to skip it
|
166
|
+
SKIP_IV_REGEXP = /^@_/
|
167
|
+
|
168
|
+
def _each_instance_variable obj, &block
|
169
|
+
obj.instance_variables.each do |iv_name|
|
170
|
+
# skipping variables starting with _xx, usually they
|
171
|
+
# have specific meaning and used for example for cache
|
172
|
+
next if iv_name =~ SKIP_IV_REGEXP
|
173
|
+
|
174
|
+
block.call iv_name, obj.instance_variable_get(iv_name)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# converts object to document (also works with nested & arrays)
|
179
|
+
def _to_document obj
|
180
|
+
return obj.to_mongo if obj.respond_to? :to_mongo
|
181
|
+
|
182
|
+
if obj.is_a? Hash
|
183
|
+
doc = {}
|
184
|
+
obj.each do |k, v|
|
185
|
+
doc[k] = _to_document v
|
186
|
+
end
|
187
|
+
doc
|
188
|
+
elsif obj.is_a? Array
|
189
|
+
obj.collect{|v| _to_document v}
|
190
|
+
elsif SIMPLE_TYPES.include? obj.class
|
191
|
+
obj
|
192
|
+
else
|
193
|
+
doc = {}
|
194
|
+
|
195
|
+
# copying instance variables
|
196
|
+
_each_instance_variable obj do |iv_name, v|
|
197
|
+
k = iv_name.to_s[1..-1]
|
198
|
+
k = k.to_sym if Mongo.defaults[:symbolize]
|
199
|
+
doc[k] = _to_document v
|
200
|
+
end
|
201
|
+
|
202
|
+
# adding _id & _class
|
203
|
+
id_key, class_key = Mongo.defaults[:symbolize] ? [:_id, :_class] : ['_id', '_class']
|
204
|
+
id = instance_variable_get('@_id')
|
205
|
+
doc[id_key] = id if id
|
206
|
+
doc[class_key] = obj.class.name
|
207
|
+
|
208
|
+
doc
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def _each_object obj, &block
|
213
|
+
if obj.is_a? Hash
|
214
|
+
obj.each{|k, v| _each_object v, &block}
|
215
|
+
elsif obj.is_a? Array
|
216
|
+
obj.each{|v| _each_object v, &block}
|
217
|
+
elsif SIMPLE_TYPES.include? obj.class
|
218
|
+
else
|
219
|
+
block.call obj
|
220
|
+
_each_instance_variable obj do |iv_name, v|
|
221
|
+
_each_object v, &block
|
222
|
+
end
|
223
|
+
end
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
class << self
|
229
|
+
def build doc
|
230
|
+
obj = _build doc
|
231
|
+
serializer = Mongo::ObjectSerializer.new obj
|
232
|
+
serializer.update_internal_state!
|
233
|
+
obj
|
234
|
+
end
|
235
|
+
|
236
|
+
protected
|
237
|
+
def _build doc
|
238
|
+
if doc.is_a? Hash
|
239
|
+
if class_name = doc[:_class] || doc['_class']
|
240
|
+
klass = constantize class_name
|
241
|
+
|
242
|
+
if klass.respond_to? :to_object
|
243
|
+
klass.to_object doc
|
244
|
+
else
|
245
|
+
obj = klass.new
|
246
|
+
doc.each do |k, v|
|
247
|
+
next if k.to_sym == :_class
|
248
|
+
|
249
|
+
v = _build v
|
250
|
+
obj.instance_variable_set "@#{k}", v
|
251
|
+
end
|
252
|
+
obj
|
253
|
+
end
|
254
|
+
else
|
255
|
+
doc
|
256
|
+
end
|
257
|
+
elsif doc.is_a? Array
|
258
|
+
doc.collect{|v| _build v}
|
259
|
+
else
|
260
|
+
doc
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def constantize class_name
|
265
|
+
@constantize_cache ||= {}
|
266
|
+
unless klass = @constantize_cache[class_name]
|
267
|
+
klass = eval class_name, TOPLEVEL_BINDING, __FILE__, __LINE__
|
268
|
+
@constantize_cache[class_name] = klass
|
269
|
+
end
|
270
|
+
klass
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|