mongo_db 0.1.8 → 0.1.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/lib/mongo_db/driver/core/collection.rb +8 -4
- data/lib/mongo_db/driver/core/database.rb +1 -1
- data/lib/mongo_db/driver/core.rb +2 -5
- data/lib/mongo_db/driver/more/collection_finders.rb +1 -1
- data/lib/mongo_db/driver/more.rb +1 -1
- data/lib/mongo_db/object/object_helper.rb +23 -123
- data/lib/mongo_db/object/object_serializer.rb +274 -0
- data/lib/mongo_db/object.rb +6 -3
- data/readme.md +25 -19
- data/spec/driver/core/hash_helper_spec.rb +1 -1
- data/spec/object/callbacks_spec.rb +95 -0
- data/spec/object/{object_crud_spec.rb → crud_spec.rb} +0 -0
- data/spec/object/spec_helper.rb +11 -1
- data/spec/object/{validation.rb → validation_spec.rb} +16 -16
- metadata +6 -7
- data/spec/driver/example_spec.rb +0 -55
- data/spec/object/callbacks.rb +0 -0
- data/spec/object/example_spec.rb +0 -64
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
module Mongo::
|
4
|
+
module Mongo::CollectionExt
|
5
5
|
#
|
6
6
|
# CRUD
|
7
7
|
#
|
@@ -38,6 +38,10 @@ module Mongo::Ext::Collection
|
|
38
38
|
def destroy *args
|
39
39
|
remove *args
|
40
40
|
end
|
41
|
+
|
42
|
+
def create *args
|
43
|
+
insert *args
|
44
|
+
end
|
41
45
|
|
42
46
|
#
|
43
47
|
# Querying
|
@@ -100,7 +104,7 @@ module Mongo::Ext::Collection
|
|
100
104
|
def symbolize_doc doc
|
101
105
|
return doc unless Mongo.defaults[:symbolize]
|
102
106
|
|
103
|
-
Mongo::
|
107
|
+
Mongo::CollectionExt.convert_doc doc do |k, v, result|
|
104
108
|
k = k.to_sym if k.is_a? String
|
105
109
|
result[k] = v
|
106
110
|
end
|
@@ -110,7 +114,7 @@ module Mongo::Ext::Collection
|
|
110
114
|
def convert_underscore_to_dollar_in_selector selector
|
111
115
|
return selector unless Mongo.defaults[:convert_underscore_to_dollar]
|
112
116
|
|
113
|
-
Mongo::
|
117
|
+
Mongo::CollectionExt.convert_doc selector do |k, v, result|
|
114
118
|
k = "$#{k.to_s[1..-1]}".to_sym if QUERY_KEYWORDS.include?(k)
|
115
119
|
result[k] = v
|
116
120
|
end
|
@@ -120,7 +124,7 @@ module Mongo::Ext::Collection
|
|
120
124
|
def convert_underscore_to_dollar_in_update update
|
121
125
|
return update unless Mongo.defaults[:convert_underscore_to_dollar]
|
122
126
|
|
123
|
-
Mongo::
|
127
|
+
Mongo::CollectionExt.convert_doc update do |k, v, result|
|
124
128
|
k = "$#{k.to_s[1..-1]}".to_sym if UPDATE_KEYWORDS.include?(k)
|
125
129
|
result[k] = v
|
126
130
|
end
|
data/lib/mongo_db/driver/core.rb
CHANGED
@@ -2,9 +2,6 @@ require 'mongo_db/gems'
|
|
2
2
|
|
3
3
|
require 'mongo'
|
4
4
|
|
5
|
-
# namespace for extensions
|
6
|
-
class Mongo::Ext; end
|
7
|
-
|
8
5
|
%w(
|
9
6
|
database
|
10
7
|
collection
|
@@ -19,11 +16,11 @@ Mongo.class_eval do
|
|
19
16
|
end
|
20
17
|
|
21
18
|
# database
|
22
|
-
Mongo::DB.send :include, Mongo::
|
19
|
+
Mongo::DB.send :include, Mongo::DBExt
|
23
20
|
|
24
21
|
# collection
|
25
22
|
Mongo::Collection.class_eval do
|
26
|
-
include Mongo::
|
23
|
+
include Mongo::CollectionExt
|
27
24
|
|
28
25
|
%w(insert update remove save).each do |method|
|
29
26
|
alias_method "#{method}_without_ext", method
|
data/lib/mongo_db/driver/more.rb
CHANGED
@@ -1,36 +1,37 @@
|
|
1
|
-
|
1
|
+
module Mongo::ObjectHelper
|
2
2
|
#
|
3
3
|
# CRUD
|
4
4
|
#
|
5
|
-
def
|
5
|
+
def save_with_object doc, opts = {}
|
6
6
|
if doc.is_a? Hash
|
7
|
-
|
7
|
+
save_without_object doc, opts
|
8
8
|
else
|
9
|
-
|
10
|
-
update({:_id => id}, doc, opts.merge(upsert: true))
|
11
|
-
else
|
12
|
-
insert doc, opts
|
13
|
-
end
|
9
|
+
::Mongo::ObjectSerializer.new(doc).save opts, self
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
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
|
26
28
|
end
|
27
29
|
|
28
|
-
def
|
30
|
+
def remove_with_object arg = {}, opts = {}
|
29
31
|
if arg.is_a? Hash
|
30
|
-
|
32
|
+
remove_without_object arg, opts
|
31
33
|
else
|
32
|
-
|
33
|
-
remove_without_model({_id: id}, opts)
|
34
|
+
::Mongo::ObjectSerializer.new(arg).remove opts, self
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -40,115 +41,14 @@
|
|
40
41
|
#
|
41
42
|
def first *args, &block
|
42
43
|
doc = super *args, &block
|
43
|
-
Mongo::
|
44
|
+
::Mongo::ObjectSerializer.build(doc)
|
44
45
|
end
|
45
46
|
|
46
47
|
def each *args, &block
|
47
48
|
super *args do |doc|
|
48
|
-
|
49
|
-
block.call
|
49
|
+
obj = ::Mongo::ObjectSerializer.build(doc)
|
50
|
+
block.call obj
|
50
51
|
end
|
51
52
|
nil
|
52
53
|
end
|
53
|
-
|
54
|
-
protected
|
55
|
-
def _insert_with_model docs, opts
|
56
|
-
hashes = docs.collect do |doc|
|
57
|
-
doc.is_a?(Hash) ? doc : Mongo::Ext::ObjectHelper.convert_object_to_doc(doc)
|
58
|
-
end
|
59
|
-
result = insert_without_model hashes, opts
|
60
|
-
hashes.each_with_index do |h, i|
|
61
|
-
Mongo::Ext::ObjectHelper.update_object_after_insertion docs[i], h
|
62
|
-
end
|
63
|
-
result
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
SIMPLE_TYPES = [
|
68
|
-
Fixnum, Float,
|
69
|
-
TrueClass, FalseClass,
|
70
|
-
String, Symbol,
|
71
|
-
Array, Hash, Set,
|
72
|
-
Data, DateTime,
|
73
|
-
NilClass, Time,
|
74
|
-
BSON::ObjectId
|
75
|
-
].to_set
|
76
|
-
|
77
|
-
class << self
|
78
|
-
def update_object_after_insertion doc, hash
|
79
|
-
return if doc.is_a? Hash
|
80
|
-
if id = hash[:_id] || hash['_id']
|
81
|
-
doc.instance_variable_set :@_id, id
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# converts object to hash (also works with nested & arrays)
|
86
|
-
def convert_object_to_doc obj
|
87
|
-
return obj.to_mongo if obj.respond_to? :to_mongo
|
88
|
-
|
89
|
-
if obj.is_a? Hash
|
90
|
-
doc = {}
|
91
|
-
obj.each do |k, v|
|
92
|
-
doc[k] = convert_object_to_doc v
|
93
|
-
end
|
94
|
-
doc
|
95
|
-
elsif obj.is_a? Array
|
96
|
-
obj.collect{|v| convert_object_to_doc v}
|
97
|
-
elsif SIMPLE_TYPES.include? obj.class
|
98
|
-
obj
|
99
|
-
else
|
100
|
-
doc = {}
|
101
|
-
|
102
|
-
# copying instance variables to hash
|
103
|
-
obj.instance_variables.each do |iv_name|
|
104
|
-
# skipping variables starting with _xx, usually they
|
105
|
-
# have specific meaning and used for example for cache
|
106
|
-
next if iv_name =~ /^@_/ and iv_name != :@_id
|
107
|
-
|
108
|
-
k = iv_name.to_s[1..-1]
|
109
|
-
k = k.to_sym if Mongo.defaults[:symbolize]
|
110
|
-
v = obj.instance_variable_get iv_name
|
111
|
-
doc[k] = convert_object_to_doc v
|
112
|
-
end
|
113
|
-
|
114
|
-
# setting class
|
115
|
-
class_name = '_class'
|
116
|
-
class_name = class_name.to_sym if Mongo.defaults[:symbolize]
|
117
|
-
doc[class_name] = obj.class.name
|
118
|
-
|
119
|
-
doc
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def convert_doc_to_object doc
|
124
|
-
if doc.is_a? Hash
|
125
|
-
if class_name = doc[:_class] || doc['_class']
|
126
|
-
klass = constantize class_name
|
127
|
-
obj = klass.new
|
128
|
-
doc.each do |k, v|
|
129
|
-
next if k.to_sym == :_class
|
130
|
-
|
131
|
-
v = convert_doc_to_object v
|
132
|
-
obj.instance_variable_set "@#{k}", v
|
133
|
-
end
|
134
|
-
obj
|
135
|
-
else
|
136
|
-
doc
|
137
|
-
end
|
138
|
-
elsif doc.is_a? Array
|
139
|
-
doc.collect{|v| convert_doc_to_object v}
|
140
|
-
else
|
141
|
-
doc
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def constantize class_name
|
146
|
-
@constantize_cache ||= {}
|
147
|
-
unless klass = @constantize_cache[class_name]
|
148
|
-
klass = eval class_name, TOPLEVEL_BINDING, __FILE__, __LINE__
|
149
|
-
@constantize_cache[class_name] = klass
|
150
|
-
end
|
151
|
-
klass
|
152
|
-
end
|
153
|
-
end
|
154
54
|
end
|
@@ -0,0 +1,274 @@
|
|
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_embedded_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_embedded_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_embedded_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, *names
|
131
|
+
all_successfull = true
|
132
|
+
names.each do |name|
|
133
|
+
objects.each do |obj|
|
134
|
+
if obj.respond_to? :_run_callbacks
|
135
|
+
all_successfull = false if obj._run_callbacks(name) == false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
all_successfull
|
140
|
+
end
|
141
|
+
|
142
|
+
def objects
|
143
|
+
@objects_cache ||= begin
|
144
|
+
objects = []
|
145
|
+
_each_object(object){|obj| objects << obj}
|
146
|
+
objects
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def update_internal_state!
|
151
|
+
self.original_embedded_objects = objects if Mongo.defaults[:callbacks]
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
def original_embedded_objects; object.instance_variable_get(:@_original_embedded_objects) end
|
156
|
+
def original_embedded_objects= objects; object.instance_variable_set(:@_original_embedded_objects, objects) end
|
157
|
+
|
158
|
+
def parse_object_options opts
|
159
|
+
opts = opts.clone
|
160
|
+
validate = opts.delete(:validate) == false ? false : true
|
161
|
+
callbacks = opts.delete(:callbacks) == false ? false : Mongo.defaults[:callbacks]
|
162
|
+
return opts, validate, callbacks
|
163
|
+
end
|
164
|
+
|
165
|
+
# need this to allow change it in specs
|
166
|
+
# RSpec adds @mock_proxy, and we need to skip it
|
167
|
+
SKIP_IV_REGEXP = /^@_/
|
168
|
+
|
169
|
+
def _each_instance_variable obj, &block
|
170
|
+
obj.instance_variables.each do |iv_name|
|
171
|
+
# skipping variables starting with _xx, usually they
|
172
|
+
# have specific meaning and used for example for cache
|
173
|
+
next if iv_name =~ SKIP_IV_REGEXP
|
174
|
+
|
175
|
+
block.call iv_name, obj.instance_variable_get(iv_name)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# converts object to document (also works with nested & arrays)
|
180
|
+
def _to_document obj
|
181
|
+
return obj.to_mongo if obj.respond_to? :to_mongo
|
182
|
+
|
183
|
+
if obj.is_a? Hash
|
184
|
+
doc = {}
|
185
|
+
obj.each do |k, v|
|
186
|
+
doc[k] = _to_document v
|
187
|
+
end
|
188
|
+
doc
|
189
|
+
elsif obj.is_a? Array
|
190
|
+
obj.collect{|v| _to_document v}
|
191
|
+
elsif SIMPLE_TYPES.include? obj.class
|
192
|
+
obj
|
193
|
+
else
|
194
|
+
doc = {}
|
195
|
+
|
196
|
+
# copying instance variables
|
197
|
+
_each_instance_variable obj do |iv_name, v|
|
198
|
+
k = iv_name.to_s[1..-1]
|
199
|
+
k = k.to_sym if Mongo.defaults[:symbolize]
|
200
|
+
doc[k] = _to_document v
|
201
|
+
end
|
202
|
+
|
203
|
+
# adding _id & _class
|
204
|
+
id_key, class_key = Mongo.defaults[:symbolize] ? [:_id, :_class] : ['_id', '_class']
|
205
|
+
id = instance_variable_get('@_id')
|
206
|
+
doc[id_key] = id if id
|
207
|
+
doc[class_key] = obj.class.name
|
208
|
+
|
209
|
+
doc
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def _each_object obj, &block
|
214
|
+
if obj.is_a? Hash
|
215
|
+
obj.each{|k, v| _each_object v, &block}
|
216
|
+
elsif obj.is_a? Array
|
217
|
+
obj.each{|v| _each_object v, &block}
|
218
|
+
elsif SIMPLE_TYPES.include? obj.class
|
219
|
+
else
|
220
|
+
block.call obj
|
221
|
+
_each_instance_variable obj do |iv_name, v|
|
222
|
+
_each_object v, &block
|
223
|
+
end
|
224
|
+
end
|
225
|
+
nil
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
class << self
|
230
|
+
def build doc
|
231
|
+
obj = _build doc
|
232
|
+
serializer = Mongo::ObjectSerializer.new obj
|
233
|
+
serializer.update_internal_state!
|
234
|
+
obj
|
235
|
+
end
|
236
|
+
|
237
|
+
protected
|
238
|
+
def _build doc
|
239
|
+
if doc.is_a? Hash
|
240
|
+
if class_name = doc[:_class] || doc['_class']
|
241
|
+
klass = constantize class_name
|
242
|
+
|
243
|
+
if klass.respond_to? :to_object
|
244
|
+
klass.to_object doc
|
245
|
+
else
|
246
|
+
obj = klass.new
|
247
|
+
doc.each do |k, v|
|
248
|
+
next if k.to_sym == :_class
|
249
|
+
|
250
|
+
v = _build v
|
251
|
+
obj.instance_variable_set "@#{k}", v
|
252
|
+
end
|
253
|
+
obj
|
254
|
+
end
|
255
|
+
else
|
256
|
+
doc
|
257
|
+
end
|
258
|
+
elsif doc.is_a? Array
|
259
|
+
doc.collect{|v| _build v}
|
260
|
+
else
|
261
|
+
doc
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def constantize class_name
|
266
|
+
@constantize_cache ||= {}
|
267
|
+
unless klass = @constantize_cache[class_name]
|
268
|
+
klass = eval class_name, TOPLEVEL_BINDING, __FILE__, __LINE__
|
269
|
+
@constantize_cache[class_name] = klass
|
270
|
+
end
|
271
|
+
klass
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
data/lib/mongo_db/object.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'mongo_db/driver'
|
2
2
|
|
3
3
|
%w(
|
4
|
+
object_serializer
|
4
5
|
object_helper
|
5
6
|
).each{|f| require "mongo_db/object/#{f}"}
|
6
7
|
|
8
|
+
Mongo.defaults[:callbacks] = true
|
9
|
+
|
7
10
|
# collection
|
8
11
|
Mongo::Collection.class_eval do
|
9
|
-
include Mongo::
|
12
|
+
include Mongo::ObjectHelper
|
10
13
|
|
11
14
|
%w(insert update remove save).each do |method|
|
12
|
-
alias_method "#{method}
|
13
|
-
alias_method method, "#{method}
|
15
|
+
alias_method "#{method}_without_object", method
|
16
|
+
alias_method method, "#{method}_with_object"
|
14
17
|
end
|
15
18
|
end
|
data/readme.md
CHANGED
@@ -18,45 +18,50 @@ These enhancements alter the driver's API and made it more simple and intuitive.
|
|
18
18
|
``` ruby
|
19
19
|
require 'mongo_db/driver/core'
|
20
20
|
|
21
|
-
#
|
21
|
+
# Changing some defaults.
|
22
22
|
Mongo.defaults.merge! symbolize: true, multi: true, safe: true
|
23
23
|
|
24
|
-
#
|
24
|
+
# Connection & db.
|
25
25
|
connection = Mongo::Connection.new
|
26
26
|
db = connection.db 'default_test'
|
27
|
+
db.units.drop
|
27
28
|
|
28
|
-
#
|
29
|
+
# Collection shortcuts.
|
30
|
+
db.some_collection
|
31
|
+
|
32
|
+
# Create.
|
29
33
|
zeratul = {name: 'Zeratul', stats: {attack: 85, life: 300, shield: 100}}
|
30
34
|
tassadar = {name: 'Tassadar', stats: {attack: 0, life: 80, shield: 300}}
|
31
35
|
|
32
36
|
db.units.save zeratul
|
33
37
|
db.units.save tassadar
|
34
38
|
|
35
|
-
#
|
39
|
+
# Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
|
36
40
|
tassadar[:stats][:attack] = 20
|
37
41
|
db.units.save tassadar
|
38
42
|
|
39
|
-
#
|
40
|
-
db.units.first name: 'Zeratul'
|
41
|
-
db.units.all name: 'Zeratul'
|
43
|
+
# Querying first & all, there's also :each, the same as :all.
|
44
|
+
db.units.first name: 'Zeratul' # => zeratul
|
45
|
+
db.units.all name: 'Zeratul' # => [zeratul]
|
42
46
|
db.units.all name: 'Zeratul' do |unit|
|
43
|
-
unit
|
47
|
+
unit # => zeratul
|
44
48
|
end
|
45
49
|
```
|
46
50
|
|
47
51
|
Optionall stuff - simple query enchancements:
|
48
52
|
|
49
53
|
``` ruby
|
54
|
+
# Finders.
|
50
55
|
require 'mongo_db/driver/more'
|
51
56
|
|
52
|
-
#
|
57
|
+
# Simple finders (bang versions also availiable).
|
53
58
|
db.units.by_name 'Zeratul' # => zeratul
|
54
59
|
db.units.first_by_name 'Zeratul' # => zeratul
|
55
60
|
db.units.all_by_name 'Zeratul' # => [zeratul]
|
56
61
|
|
57
|
-
#
|
62
|
+
# Query sugar, use {name: {_gt: 'Z'}} instead of {name: {:$gt => 'Z'}}.
|
58
63
|
Mongo.defaults.merge! convert_underscore_to_dollar: true
|
59
|
-
db.units.all
|
64
|
+
db.units.all name: {_gt: 'Z'} # => [zeratul]
|
60
65
|
```
|
61
66
|
|
62
67
|
More docs - there's no need for more docs, the whole point of this extension is to be small, intuitive, 100% compatible with the official driver, and require no extra knowledge.
|
@@ -69,7 +74,7 @@ Save any Ruby object to MongoDB, as if it's a document. Objects can be any type,
|
|
69
74
|
Note: the :initialize method should allow to create object without arguments.
|
70
75
|
|
71
76
|
``` ruby
|
72
|
-
#
|
77
|
+
# Let's define the game unit.
|
73
78
|
class Unit
|
74
79
|
attr_reader :name, :stats
|
75
80
|
|
@@ -87,38 +92,39 @@ class Unit
|
|
87
92
|
end
|
88
93
|
end
|
89
94
|
|
90
|
-
#
|
95
|
+
# Connecting to MongoDB.
|
91
96
|
require 'mongo_db/object'
|
92
97
|
Mongo.defaults.merge! symbolize: true, multi: true, safe: true
|
93
98
|
connection = Mongo::Connection.new
|
94
99
|
db = connection.db 'default_test'
|
100
|
+
db.units.drop
|
95
101
|
|
96
|
-
#
|
102
|
+
# Create.
|
97
103
|
zeratul = Unit.new('Zeratul', Unit::Stats.new(85, 300, 100))
|
98
104
|
tassadar = Unit.new('Tassadar', Unit::Stats.new(0, 80, 300))
|
99
105
|
|
100
106
|
db.units.save zeratul
|
101
107
|
db.units.save tassadar
|
102
108
|
|
103
|
-
#
|
109
|
+
# Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
|
104
110
|
tassadar.stats.attack = 20
|
105
111
|
db.units.save tassadar
|
106
112
|
|
107
|
-
#
|
113
|
+
# Querying first & all, there's also :each, the same as :all.
|
108
114
|
db.units.first name: 'Zeratul' # => zeratul
|
109
115
|
db.units.all name: 'Zeratul' # => [zeratul]
|
110
116
|
db.units.all name: 'Zeratul' do |unit|
|
111
117
|
unit # => zeratul
|
112
118
|
end
|
113
119
|
|
114
|
-
#
|
120
|
+
# Simple finders (bang versions also availiable).
|
115
121
|
db.units.by_name 'Zeratul' # => zeratul
|
116
122
|
db.units.first_by_name 'Zeratul' # => zeratul
|
117
123
|
db.units.all_by_name 'Zeratul' # => [zeratul]
|
118
124
|
|
119
|
-
#
|
125
|
+
# Query sugar, use {name: {_gt: 'Z'}} instead of {name: {:$gt => 'Z'}}.
|
120
126
|
Mongo.defaults.merge! convert_underscore_to_dollar: true
|
121
|
-
db.units.all
|
127
|
+
db.units.all name: {_gt: 'Z'} # => [zeratul]
|
122
128
|
```
|
123
129
|
|
124
130
|
# Migrations (work in progress)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'object/spec_helper'
|
2
|
+
|
3
|
+
describe 'Object callbacks' do
|
4
|
+
with_mongo
|
5
|
+
|
6
|
+
before do
|
7
|
+
class Player
|
8
|
+
include RSpec::CallbackHelper
|
9
|
+
attr_accessor :missions
|
10
|
+
|
11
|
+
class Mission
|
12
|
+
include RSpec::CallbackHelper
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@mission = Player::Mission.new
|
17
|
+
@player = Player.new
|
18
|
+
@player.missions = [@mission]
|
19
|
+
end
|
20
|
+
after{remove_constants :Player}
|
21
|
+
|
22
|
+
it 'create' do
|
23
|
+
%w(before_validate before_save before_create after_create after_save after_validate).each do |name|
|
24
|
+
@player.should_receive(name).once.ordered
|
25
|
+
@mission.should_receive(name).once.ordered
|
26
|
+
end
|
27
|
+
|
28
|
+
db.players.save @player
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'update' do
|
32
|
+
db.players.save(@player)
|
33
|
+
|
34
|
+
%w(before_validate before_save before_update after_update after_save after_validate).each do |name|
|
35
|
+
@player.should_receive(name).once.ordered
|
36
|
+
@mission.should_receive(name).once.ordered
|
37
|
+
end
|
38
|
+
db.players.save @player
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'destroy' do
|
42
|
+
db.players.save @player
|
43
|
+
|
44
|
+
%w(before_validate before_destroy after_destroy after_validate).each do |name|
|
45
|
+
@player.should_receive(name).once.ordered
|
46
|
+
@mission.should_receive(name).once.ordered
|
47
|
+
end
|
48
|
+
db.players.destroy @player
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should be able skip callbacks' do
|
52
|
+
@player.should_not_receive(:_run_callbacks)
|
53
|
+
@mission.should_not_receive(:_run_callbacks)
|
54
|
+
|
55
|
+
db.players.save @player, callbacks: false
|
56
|
+
db.players.count.should == 1
|
57
|
+
db.players.save @player, callbacks: false
|
58
|
+
db.players.count.should == 1
|
59
|
+
db.players.destroy @player, callbacks: false
|
60
|
+
db.players.count.should == 0
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be able interrupt CRUD' do
|
64
|
+
@mission.stub! :_run_callbacks do |name|
|
65
|
+
false if name == :before_save
|
66
|
+
end
|
67
|
+
db.players.save(@player).should be_false
|
68
|
+
db.players.count.should == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "embedded" do
|
72
|
+
it 'should fire :destroy on detached objects' do
|
73
|
+
db.players.save @player
|
74
|
+
@player.missions.clear
|
75
|
+
@mission.should_receive(:before_destroy).once
|
76
|
+
db.players.destroy @player
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should fire :destroy on deleted objects in update' do
|
80
|
+
db.players.save @player
|
81
|
+
@player.missions.clear
|
82
|
+
@mission.should_receive(:before_destroy).once
|
83
|
+
db.players.save @player
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should fire :create on new objects in update' do
|
87
|
+
db.players.save @player
|
88
|
+
mission2 = Player::Mission.new
|
89
|
+
@player.missions << mission2
|
90
|
+
mission2.should_receive(:before_create).once
|
91
|
+
mission2.should_not_receive(:before_update)
|
92
|
+
db.players.save @player
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
File without changes
|
data/spec/object/spec_helper.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
require 'mongo_db/object'
|
2
|
+
require 'driver/spec_helper'
|
2
3
|
|
3
|
-
|
4
|
+
# RSpec adds some instance variables and we need to skip it.
|
5
|
+
Mongo::ObjectSerializer.send :const_set, :SKIP_IV_REGEXP, /^@_|^@mock_proxy/
|
6
|
+
Mongo::ObjectSerializer::SIMPLE_TYPES << RSpec::Mocks::Proxy
|
7
|
+
|
8
|
+
# To simplify callback expectations
|
9
|
+
module RSpec::CallbackHelper
|
10
|
+
def _run_callbacks name
|
11
|
+
send name if respond_to? name
|
12
|
+
end
|
13
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'spec_helper'
|
1
|
+
require 'object/spec_helper'
|
2
2
|
|
3
3
|
describe 'Object validation' do
|
4
4
|
with_mongo
|
@@ -19,56 +19,56 @@ describe 'Object validation' do
|
|
19
19
|
|
20
20
|
it 'should not save/update/destroy invalid objects' do
|
21
21
|
# create
|
22
|
-
@player.stub!(:
|
22
|
+
@player.stub!(:_valid?).and_return(false)
|
23
23
|
db.players.save(@player).should be_false
|
24
24
|
|
25
|
-
@player.stub!(:
|
25
|
+
@player.stub!(:_valid?).and_return(true)
|
26
26
|
db.players.save(@player).should be_true
|
27
27
|
|
28
28
|
# update
|
29
|
-
@player.stub!(:
|
29
|
+
@player.stub!(:_valid?).and_return(false)
|
30
30
|
db.players.save(@player).should be_false
|
31
31
|
|
32
|
-
@player.stub!(:
|
32
|
+
@player.stub!(:_valid?).and_return(true)
|
33
33
|
db.players.save(@player).should be_true
|
34
34
|
|
35
35
|
# destroy
|
36
|
-
@player.stub!(:
|
36
|
+
@player.stub!(:_valid?).and_return(false)
|
37
37
|
db.players.destroy(@player).should be_false
|
38
38
|
|
39
|
-
@player.stub!(:
|
39
|
+
@player.stub!(:_valid?).and_return(true)
|
40
40
|
db.players.destroy(@player).should be_true
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'should not save/update/destroy invalid embedded objects' do
|
44
44
|
# create
|
45
|
-
@mission.stub!(:
|
45
|
+
@mission.stub!(:_valid?).and_return(false)
|
46
46
|
db.players.save(@player).should be_false
|
47
47
|
|
48
|
-
@mission.stub!(:
|
48
|
+
@mission.stub!(:_valid?).and_return(true)
|
49
49
|
db.players.save(@player).should be_true
|
50
50
|
|
51
51
|
# update
|
52
|
-
@mission.stub!(:
|
52
|
+
@mission.stub!(:_valid?).and_return(false)
|
53
53
|
db.players.save(@player).should be_false
|
54
54
|
|
55
|
-
@mission.stub!(:
|
55
|
+
@mission.stub!(:_valid?).and_return(true)
|
56
56
|
db.players.save(@player).should be_true
|
57
57
|
|
58
58
|
# destroy
|
59
|
-
@mission.stub!(:
|
59
|
+
@mission.stub!(:_valid?).and_return(false)
|
60
60
|
db.players.destroy(@player).should be_false
|
61
61
|
|
62
|
-
@mission.stub!(:
|
62
|
+
@mission.stub!(:_valid?).and_return(true)
|
63
63
|
db.players.destroy(@player).should be_true
|
64
64
|
end
|
65
65
|
|
66
66
|
it "should be able skip validation" do
|
67
|
-
@player.stub!(:
|
67
|
+
@player.stub!(:_valid?).and_return(false)
|
68
68
|
db.players.save(@player, validate: false).should be_true
|
69
69
|
|
70
|
-
@player.stub!(:
|
71
|
-
@mission.stub!(:
|
70
|
+
@player.stub!(:_valid?).and_return(true)
|
71
|
+
@mission.stub!(:_valid?).and_return(false)
|
72
72
|
db.players.save(@player, validate: false).should be_true
|
73
73
|
end
|
74
74
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo_db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-08-
|
12
|
+
date: 2011-08-18 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mongo
|
@@ -39,12 +39,12 @@ files:
|
|
39
39
|
- lib/mongo_db/driver.rb
|
40
40
|
- lib/mongo_db/gems.rb
|
41
41
|
- lib/mongo_db/object/object_helper.rb
|
42
|
+
- lib/mongo_db/object/object_serializer.rb
|
42
43
|
- lib/mongo_db/object.rb
|
43
44
|
- spec/driver/core/collection_spec.rb
|
44
45
|
- spec/driver/core/crud_spec.rb
|
45
46
|
- spec/driver/core/database_spec.rb
|
46
47
|
- spec/driver/core/hash_helper_spec.rb
|
47
|
-
- spec/driver/example_spec.rb
|
48
48
|
- spec/driver/fixes_spec.rb
|
49
49
|
- spec/driver/more/querying_spec.rb
|
50
50
|
- spec/driver/spec_helper.rb
|
@@ -52,12 +52,11 @@ files:
|
|
52
52
|
- spec/model/callbacks.rb
|
53
53
|
- spec/model/example.rb
|
54
54
|
- spec/model/model_crud.rb
|
55
|
-
- spec/object/
|
55
|
+
- spec/object/callbacks_spec.rb
|
56
56
|
- spec/object/crud_shared.rb
|
57
|
-
- spec/object/
|
58
|
-
- spec/object/object_crud_spec.rb
|
57
|
+
- spec/object/crud_spec.rb
|
59
58
|
- spec/object/spec_helper.rb
|
60
|
-
- spec/object/
|
59
|
+
- spec/object/validation_spec.rb
|
61
60
|
- spec/test.rb
|
62
61
|
homepage: http://github.com/alexeypetrushin/mongo_db
|
63
62
|
licenses: []
|
data/spec/driver/example_spec.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'mongo_db/driver/core'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe "Driver example" do
|
5
|
-
defaults = nil
|
6
|
-
before(:all){defaults = Mongo.defaults.clone}
|
7
|
-
after(:all){Mongo.defaults = defaults}
|
8
|
-
|
9
|
-
it "core" do
|
10
|
-
require 'mongo_db/driver/core'
|
11
|
-
|
12
|
-
# changing some defaults
|
13
|
-
Mongo.defaults.merge! symbolize: true, multi: true, safe: true
|
14
|
-
|
15
|
-
# connection & db
|
16
|
-
connection = Mongo::Connection.new
|
17
|
-
db = connection.db 'default_test'
|
18
|
-
|
19
|
-
# collection shortcuts
|
20
|
-
db.some_collection
|
21
|
-
|
22
|
-
# create
|
23
|
-
zeratul = {name: 'Zeratul', stats: {attack: 85, life: 300, shield: 100}}
|
24
|
-
tassadar = {name: 'Tassadar', stats: {attack: 0, life: 80, shield: 300}}
|
25
|
-
|
26
|
-
db.units.save zeratul
|
27
|
-
db.units.save tassadar
|
28
|
-
|
29
|
-
# udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it)
|
30
|
-
tassadar[:stats][:attack] = 20
|
31
|
-
db.units.save tassadar
|
32
|
-
|
33
|
-
# querying first & all, there's also :each, the same as :all
|
34
|
-
db.units.first name: 'Zeratul' # => zeratul
|
35
|
-
db.units.all name: 'Zeratul' # => [zeratul]
|
36
|
-
db.units.all name: 'Zeratul' do |unit|
|
37
|
-
unit # => zeratul
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
#
|
42
|
-
# optional
|
43
|
-
#
|
44
|
-
require 'mongo_db/driver/more'
|
45
|
-
|
46
|
-
# simple finders (bang versions also availiable)
|
47
|
-
db.units.by_name 'Zeratul' # => zeratul
|
48
|
-
db.units.first_by_name 'Zeratul' # => zeratul
|
49
|
-
db.units.all_by_name 'Zeratul' # => [zeratul]
|
50
|
-
|
51
|
-
# query sugar, use {life: {_lt: 100}} instead of {life: {:$lt => 100}}
|
52
|
-
Mongo.defaults.merge! convert_underscore_to_dollar: true
|
53
|
-
db.units.all 'stats.life' => {_lt: 100} # => [tassadar]
|
54
|
-
end
|
55
|
-
end
|
data/spec/object/callbacks.rb
DELETED
File without changes
|
data/spec/object/example_spec.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'mongo_db/object'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe "Object example" do
|
5
|
-
defaults = nil
|
6
|
-
before(:all){defaults = Mongo.defaults.clone}
|
7
|
-
after :all do
|
8
|
-
Mongo.defaults = defaults
|
9
|
-
Object.send :remove_const, :Unit if Object.const_defined? :Unit
|
10
|
-
end
|
11
|
-
|
12
|
-
it do
|
13
|
-
# let's define the game unit
|
14
|
-
class Unit
|
15
|
-
attr_reader :name, :stats
|
16
|
-
|
17
|
-
# don't forget to allow creating object with no arguments
|
18
|
-
def initialize name = nil, stats = nil
|
19
|
-
@name, @stats = name, stats
|
20
|
-
end
|
21
|
-
|
22
|
-
class Stats
|
23
|
-
attr_accessor :attack, :life, :shield
|
24
|
-
|
25
|
-
def initialize attack = nil, life = nil, shield = nil
|
26
|
-
@attack, @life, @shield = attack, life, shield
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# connecting to MongoDB
|
32
|
-
require 'mongo_db/object'
|
33
|
-
Mongo.defaults.merge! symbolize: true, multi: true, safe: true
|
34
|
-
connection = Mongo::Connection.new
|
35
|
-
db = connection.db 'default_test'
|
36
|
-
|
37
|
-
# create
|
38
|
-
zeratul = Unit.new('Zeratul', Unit::Stats.new(85, 300, 100))
|
39
|
-
tassadar = Unit.new('Tassadar', Unit::Stats.new(0, 80, 300))
|
40
|
-
|
41
|
-
db.units.save zeratul
|
42
|
-
db.units.save tassadar
|
43
|
-
|
44
|
-
# udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it)
|
45
|
-
tassadar.stats.attack = 20
|
46
|
-
db.units.save tassadar
|
47
|
-
|
48
|
-
# querying first & all, there's also :each, the same as :all
|
49
|
-
db.units.first name: 'Zeratul' # => zeratul
|
50
|
-
db.units.all name: 'Zeratul' # => [zeratul]
|
51
|
-
db.units.all name: 'Zeratul' do |unit|
|
52
|
-
unit # => zeratul
|
53
|
-
end
|
54
|
-
|
55
|
-
# simple finders (bang versions also availiable)
|
56
|
-
db.units.by_name 'Zeratul' # => zeratul
|
57
|
-
db.units.first_by_name 'Zeratul' # => zeratul
|
58
|
-
db.units.all_by_name 'Zeratul' # => [zeratul]
|
59
|
-
|
60
|
-
# query sugar, use {life: {_lt: 100}} instead of {life: {:$lt => 100}}
|
61
|
-
Mongo.defaults.merge! convert_underscore_to_dollar: true
|
62
|
-
db.units.all('stats.life' => {_lt: 100}) # => [tassadar]
|
63
|
-
end
|
64
|
-
end
|