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.
Files changed (74) hide show
  1. data/Rakefile +3 -5
  2. data/lib/mongodb/driver.rb +33 -0
  3. data/lib/mongodb/driver/collection.rb +156 -0
  4. data/lib/mongodb/driver/database.rb +6 -0
  5. data/lib/mongodb/driver/dynamic_finders.rb +41 -0
  6. data/lib/{mongo_db → mongodb}/driver/spec.rb +12 -12
  7. data/lib/mongodb/gems.rb +6 -0
  8. data/lib/mongodb/integration/locales.rb +4 -0
  9. data/lib/mongodb/integration/locales/activemodel/ru.yml +27 -0
  10. data/lib/mongodb/migration.rb +8 -0
  11. data/lib/mongodb/migration/definition.rb +19 -0
  12. data/lib/mongodb/migration/migration.rb +68 -0
  13. data/lib/mongodb/migration/tasks.rb +19 -0
  14. data/lib/mongodb/model.rb +26 -0
  15. data/lib/mongodb/model/assignment.rb +65 -0
  16. data/lib/mongodb/model/attribute_convertors.rb +54 -0
  17. data/lib/mongodb/model/callbacks.rb +36 -0
  18. data/lib/mongodb/model/crud.rb +57 -0
  19. data/lib/mongodb/model/db.rb +53 -0
  20. data/lib/mongodb/model/misc.rb +33 -0
  21. data/lib/mongodb/model/model.rb +11 -0
  22. data/lib/mongodb/model/query.rb +36 -0
  23. data/lib/mongodb/model/scope.rb +99 -0
  24. data/lib/mongodb/model/spec.rb +12 -0
  25. data/lib/mongodb/model/support/types.rb +110 -0
  26. data/lib/mongodb/model/validation.rb +5 -0
  27. data/lib/mongodb/object.rb +18 -0
  28. data/lib/mongodb/object/object_helper.rb +62 -0
  29. data/lib/mongodb/object/object_serializer.rb +273 -0
  30. data/readme.md +261 -6
  31. data/spec/driver/collection_spec.rb +83 -0
  32. data/spec/{mongo_model/hash → driver}/crud_spec.rb +30 -29
  33. data/spec/driver/database_spec.rb +9 -0
  34. data/spec/driver/dynamic_finders_spec.rb +50 -0
  35. data/spec/driver/fixes_spec.rb +12 -0
  36. data/spec/driver/hash_helper_spec.rb +24 -0
  37. data/spec/driver/spec_helper.rb +28 -0
  38. data/spec/integration/am_conversion_spec.rb +1 -0
  39. data/spec/integration/am_validation_spec.rb +34 -0
  40. data/spec/integration/validatable2_spec.rb +40 -0
  41. data/spec/migration/migration_spec.rb +60 -0
  42. data/spec/model/assignment_spec.rb +80 -0
  43. data/spec/model/attribute_convertors_spec.rb +73 -0
  44. data/spec/model/callbacks_spec.rb +47 -0
  45. data/spec/model/crud_spec.rb +151 -0
  46. data/spec/model/db_spec.rb +63 -0
  47. data/spec/model/misc_spec.rb +58 -0
  48. data/spec/model/query_spec.rb +47 -0
  49. data/spec/model/scope_spec.rb +149 -0
  50. data/spec/model/spec_helper.rb +4 -0
  51. data/spec/model/validation_spec.rb +37 -0
  52. data/spec/object/callbacks_spec.rb +97 -0
  53. data/spec/object/crud_shared.rb +53 -0
  54. data/spec/object/crud_spec.rb +55 -0
  55. data/spec/object/spec_helper.rb +14 -0
  56. data/spec/{mongo_model/object → object}/validation_spec.rb +38 -36
  57. metadata +92 -25
  58. data/lib/mongo_db.rb +0 -3
  59. data/lib/mongo_db/driver.rb +0 -5
  60. data/lib/mongo_db/driver/connection.rb +0 -0
  61. data/lib/mongo_db/driver/database.rb +0 -5
  62. data/lib/mongo_db/gems.rb +0 -2
  63. data/lib/mongo_db/model.rb +0 -0
  64. data/spec/mongo_ext/migration_spec.rb +0 -0
  65. data/spec/mongo_ext/misc_spec.rb +0 -10
  66. data/spec/mongo_ext/spec_helper.rb +0 -4
  67. data/spec/mongo_model/model/crud_spec.rb +0 -123
  68. data/spec/mongo_model/model/query_spec.rb +0 -0
  69. data/spec/mongo_model/object/callbacks_spec.rb +0 -100
  70. data/spec/mongo_model/object/crud_shared.rb +0 -53
  71. data/spec/mongo_model/object/crud_spec.rb +0 -45
  72. data/spec/mongo_model/spec_helper.rb +0 -1
  73. data/spec/query_spec.rb +0 -0
  74. data/spec/test_spec.rb +0 -5
@@ -0,0 +1,12 @@
1
+ require 'mongodb/driver/spec'
2
+
3
+ rspec do
4
+ class << self
5
+ def with_mongo_model
6
+ with_mongo
7
+
8
+ before{Mongo::Model.db = mongo.db}
9
+ after{Mongo::Model.db = nil}
10
+ end
11
+ end
12
+ end
@@ -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,5 @@
1
+ module Mongo::Model::Validation
2
+ def _valid?
3
+ !(respond_to?(:errors) and errors and !errors.empty?)
4
+ end
5
+ 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