mongoo 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ module Mongoo
2
+ module Changelog
3
+
4
+ def changelog
5
+ persisted_mongohash_kv = (self.persisted_mongohash || Mongoo::Mongohash.new).to_key_value
6
+ curr_mongohash_kv = self.mongohash.to_key_value
7
+
8
+ log = []
9
+
10
+ persisted_mongohash_kv.each do |k,v|
11
+ unless curr_mongohash_kv.has_key?(k)
12
+ found = nil
13
+ parts = k.split(".")
14
+ while parts.pop
15
+ if !self.mongohash.dot_get(parts.join("."))
16
+ found = [:unset, parts.join("."), 1]
17
+ end
18
+ end
19
+ found ||= [:unset, k, 1]
20
+ log << found
21
+ end
22
+ end
23
+
24
+ curr_mongohash_kv.each do |k,v|
25
+ if v != persisted_mongohash_kv[k]
26
+ unless log.include?([:set, k, v])
27
+ log << [:set, k, v]
28
+ end
29
+ end
30
+ end
31
+
32
+ log.uniq
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,55 @@
1
+ module Mongoo
2
+ class Cursor
3
+ include Enumerable
4
+
5
+ attr_accessor :mongo_cursor
6
+
7
+ def initialize(obj_class, mongo_cursor)
8
+ @obj_class = obj_class
9
+ @mongo_cursor = mongo_cursor
10
+ end
11
+
12
+ def next_document
13
+ if doc = @mongo_cursor.next_document
14
+ @obj_class.new(doc, true)
15
+ end
16
+ end
17
+
18
+ alias :next :next_document
19
+
20
+ def each
21
+ @mongo_cursor.each do |doc|
22
+ yield(@obj_class.new(doc, true))
23
+ end
24
+ end
25
+
26
+ def sort(key_or_list, direction=nil)
27
+ @mongo_cursor.sort(key_or_list, direction)
28
+ self
29
+ end
30
+
31
+ def limit(number_to_return=nil)
32
+ @mongo_cursor.limit(number_to_return)
33
+ self
34
+ end
35
+
36
+ def skip(number_to_return=nil)
37
+ @mongo_cursor.skip(number_to_return)
38
+ self
39
+ end
40
+
41
+ def batch_size(size=0)
42
+ @mongo_cursor.batch_size(size)
43
+ self
44
+ end
45
+
46
+ def method_missing(name, *args)
47
+ if @mongo_cursor.respond_to?(name)
48
+ @mongo_cursor.send name, *args
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ module Mongoo
2
+ module HashExt
3
+ def deep_stringify_keys
4
+ Marshal.load(Marshal.dump(self)).deep_stringify_keys!
5
+ end
6
+
7
+ def deep_stringify_keys!
8
+ keys.each do |key|
9
+ self[key.to_s] = delete(key)
10
+ if self[key.to_s].is_a?(Hash)
11
+ self[key.to_s].stringify_keys!
12
+ end
13
+ end
14
+ self
15
+ end
16
+ end
17
+ end
18
+
19
+ class Hash
20
+ include Mongoo::HashExt
21
+ end
@@ -0,0 +1,150 @@
1
+ module Mongoo
2
+ class ModifierUpdateError < Exception; end
3
+ class UnknownAttributeError < Exception; end
4
+
5
+ class ModifierBuilder
6
+ def initialize(opts, doc)
7
+ @opts = opts
8
+ @doc = doc
9
+ @queue = {}
10
+ @key_prefix = opts[:key_prefix] || ""
11
+ end
12
+
13
+ def ensure_valid_field!(k)
14
+ unless @doc.known_attribute?("#{@key_prefix}#{k}")
15
+ raise UnknownAttributeError, "#{@key_prefix}#{k}"
16
+ end
17
+ end
18
+
19
+ def sanitize_value(k,v)
20
+ k = "#{@key_prefix}#{k}"
21
+ field_type = @doc.class.attributes[k][:type]
22
+ Mongoo::AttributeSanitizer.sanitize(field_type, v)
23
+ end
24
+
25
+ def inc(k, v=1)
26
+ ensure_valid_field!(k)
27
+ v = sanitize_value(k,v)
28
+ @queue["$inc"] ||= {}
29
+ @queue["$inc"]["#{@key_prefix}#{k}"] = v
30
+ end
31
+
32
+ def set(k,v)
33
+ ensure_valid_field!(k)
34
+ v = sanitize_value(k,v)
35
+ @queue["$set"] ||= {}
36
+ @queue["$set"]["#{@key_prefix}#{k}"] = v
37
+ end
38
+
39
+ def unset(k)
40
+ ensure_valid_field!(k)
41
+ @queue["$unset"] ||= {}
42
+ @queue["$unset"]["#{@key_prefix}#{k}"] = 1
43
+ end
44
+
45
+ def push(k, v)
46
+ ensure_valid_field!(k)
47
+ @queue["$push"] ||= {}
48
+ @queue["$push"]["#{@key_prefix}#{k}"] = v
49
+ end
50
+
51
+ def push_all(k, v)
52
+ ensure_valid_field!(k)
53
+ @queue["$pushAll"] ||= {}
54
+ @queue["$pushAll"]["#{@key_prefix}#{k}"] = v
55
+ end
56
+
57
+ def add_to_set(k,v)
58
+ ensure_valid_field!(k)
59
+ @queue["$addToSet"] ||= {}
60
+ @queue["$addToSet"]["#{@key_prefix}#{k}"] = v
61
+ end
62
+
63
+ def pop(k)
64
+ ensure_valid_field!(k)
65
+ @queue["$pop"] ||= {}
66
+ @queue["$pop"]["#{@key_prefix}#{k}"] = 1
67
+ end
68
+
69
+ def pull(k, v)
70
+ ensure_valid_field!(k)
71
+ @queue["$pull"] ||= {}
72
+ @queue["$pull"]["#{@key_prefix}#{k}"] = v
73
+ end
74
+
75
+ def pull_all(k, v)
76
+ ensure_valid_field!(k)
77
+ @queue["$pullAll"] ||= {}
78
+ @queue["$pullAll"]["#{@key_prefix}#{k}"] = v
79
+ end
80
+
81
+ def run!
82
+ ret = @doc.collection.update({"_id" => @doc.id}, @queue, @opts)
83
+ if !ret.is_a?(Hash) || (ret["err"] == nil && ret["n"] == 1)
84
+ @queue.each do |op, op_queue|
85
+ op_queue.each do |k, v|
86
+ case op
87
+ when "$inc" then
88
+ new_val = @doc.persisted_mongohash.dot_get(k).to_i + v
89
+ @doc.mongohash.dot_set( k, new_val )
90
+ @doc.persisted_mongohash.dot_set( k, new_val )
91
+ when "$set" then
92
+ @doc.mongohash.dot_set( k, v )
93
+ @doc.persisted_mongohash.dot_set( k, v )
94
+ when "$unset" then
95
+ @doc.mongohash.dot_delete( k )
96
+ @doc.persisted_mongohash.dot_delete( k )
97
+ when "$push" then
98
+ new_val = (@doc.persisted_mongohash.dot_get(k) || []) + [v]
99
+ @doc.mongohash.dot_set( k, new_val )
100
+ @doc.persisted_mongohash.dot_set( k, new_val )
101
+ when "$pushAll" then
102
+ new_val = (@doc.persisted_mongohash.dot_get(k) || []) + v
103
+ @doc.mongohash.dot_set( k, new_val )
104
+ @doc.persisted_mongohash.dot_set( k, new_val )
105
+ when "$addToSet" then
106
+ new_val = (@doc.persisted_mongohash.dot_get(k) || [])
107
+ new_val << v unless new_val.include?(v)
108
+ @doc.mongohash.dot_set(k, new_val)
109
+ @doc.persisted_mongohash.dot_set(k, new_val)
110
+ when "$pop" then
111
+ new_val = (@doc.persisted_mongohash.dot_get(k) || [])
112
+ new_val.pop
113
+ @doc.mongohash.dot_set(k, new_val)
114
+ @doc.persisted_mongohash.dot_set(k, new_val)
115
+ when "$pull" then
116
+ new_val = (@doc.persisted_mongohash.dot_get(k) || [])
117
+ new_val.delete(v)
118
+ @doc.mongohash.dot_set(k, new_val)
119
+ @doc.persisted_mongohash.dot_set(k, new_val)
120
+ when "$pullAll" then
121
+ new_val = (@doc.persisted_mongohash.dot_get(k) || [])
122
+ v.each do |val|
123
+ new_val.delete(val)
124
+ end
125
+ @doc.mongohash.dot_set(k, new_val)
126
+ @doc.persisted_mongohash.dot_set(k, new_val)
127
+ end
128
+ end
129
+ end
130
+ true
131
+ else
132
+ raise ModifierUpdateError, ret.inspect
133
+ end
134
+ end
135
+ end
136
+
137
+ module Modifiers
138
+
139
+ def mod(opts={}, &block)
140
+ builder = ModifierBuilder.new(opts, self)
141
+ block.call(builder)
142
+ builder.run!
143
+ end
144
+
145
+ def mod!(opts={}, &block)
146
+ mod(opts.merge(:safe => true), &block)
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,79 @@
1
+ module Mongoo
2
+ class Mongohash
3
+ extend Forwardable
4
+
5
+ def_delegators :@raw_hash, :==, :[], :[], :[]=, :clear, :default, :default=, :default_proc, :delete, :delete_if,
6
+ :each, :each_key, :each_pair, :each_value, :empty?, :fetch, :has_key?, :has_value?, :include?,
7
+ :index, :indexes, :indices, :initialize_copy, :inspect, :invert, :key?, :keys, :length, :member?,
8
+ :merge, :merge!, :pretty_print, :pretty_print_cycle, :rehash, :reject, :reject!, :replace, :select,
9
+ :shift, :size, :sort, :store, :to_a, :to_hash, :to_s, :update, :value?, :values, :values_at
10
+
11
+ attr_reader :raw_hash
12
+
13
+ def initialize(hash={})
14
+ hash = hash.to_hash unless hash.class.to_s == "Hash"
15
+ @raw_hash = hash.deep_stringify_keys
16
+ end
17
+
18
+ def deep_clone
19
+ Mongoo::Mongohash.new(Marshal.load(Marshal.dump self.raw_hash))
20
+ end
21
+
22
+ def dot_set(k,v)
23
+ parts = k.to_s.split(".")
24
+ curr_val = to_hash
25
+ while !parts.empty?
26
+ part = parts.shift
27
+ if parts.empty?
28
+ curr_val[part] = v
29
+ else
30
+ curr_val[part] ||= {}
31
+ curr_val = curr_val[part]
32
+ end
33
+ end
34
+ true
35
+ end
36
+
37
+ def dot_get(k)
38
+ parts = k.to_s.split(".")
39
+ curr_val = to_hash
40
+ while !parts.empty?
41
+ part = parts.shift
42
+ curr_val = curr_val[part]
43
+ return curr_val unless curr_val.is_a?(Hash)
44
+ end
45
+ curr_val
46
+ end
47
+
48
+ def dot_delete(k)
49
+ parts = k.to_s.split(".")
50
+ curr_val = to_hash
51
+ while !parts.empty?
52
+ part = parts.shift
53
+ if parts.empty?
54
+ curr_val.delete(part)
55
+ return true
56
+ else
57
+ curr_val = curr_val[part]
58
+ end
59
+ end
60
+ false
61
+ end
62
+
63
+ def dot_list(curr_hash=self.to_hash, path=[])
64
+ list = []
65
+ curr_hash.each do |k,v|
66
+ if v.is_a?(Hash)
67
+ list.concat dot_list(v, (path + [k]))
68
+ else
69
+ list << (path + [k]).join(".")
70
+ end
71
+ end
72
+ list
73
+ end
74
+
75
+ def to_key_value
76
+ kv = {}; dot_list.collect { |k| kv[k] = dot_get(k) }; kv
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,256 @@
1
+ module Mongoo
2
+ class AlreadyInsertedError < Exception; end
3
+ class NotInsertedError < Exception; end
4
+ class InsertError < Exception; end
5
+ class StaleUpdateError < Exception; end
6
+ class UpdateError < Exception; end
7
+ class RemoveError < Exception; end
8
+ class NotValidError < Exception; end
9
+
10
+ module Persistence
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ def collection_name
18
+ @collection_name ||= self.model_name.tableize
19
+ end
20
+
21
+ def collection
22
+ @collection ||= db.collection(collection_name)
23
+ end
24
+
25
+ def conn
26
+ @conn ||= begin
27
+ if Mongoo.config == config
28
+ Mongoo.conn
29
+ else
30
+ Mongo::Connection.new(config[:host], config[:port], config[:opts])
31
+ end
32
+ end
33
+ end
34
+
35
+ def db
36
+ @db ||= begin
37
+ if Mongoo.config == config
38
+ Mongoo.db
39
+ else
40
+ conn.db(config[:db])
41
+ end
42
+ end
43
+ end
44
+
45
+ def config=(cfg)
46
+ @config = cfg
47
+ end
48
+
49
+ def config
50
+ @config || Mongoo.config
51
+ end
52
+
53
+ def db=(db)
54
+ @db = db
55
+ end
56
+
57
+ def find(query={}, opts={})
58
+ Mongoo::Cursor.new(self, collection.find(query, opts))
59
+ end
60
+
61
+ def find_one(query={}, opts={})
62
+ return nil unless doc = collection.find_one(query, opts)
63
+ new(doc, true)
64
+ end
65
+
66
+ def all
67
+ find
68
+ end
69
+
70
+ def each
71
+ find.each { |found| yield(found) }
72
+ end
73
+
74
+ def first
75
+ find.limit(1).next_document
76
+ end
77
+
78
+ def empty?
79
+ count == 0
80
+ end
81
+
82
+ def count
83
+ collection.count
84
+ end
85
+
86
+ def drop
87
+ collection.drop
88
+ end
89
+
90
+ def index_meta
91
+ Mongoo::INDEX_META[self.collection_name] ||= {}
92
+ end
93
+
94
+ def index(spec, opts={})
95
+ self.index_meta[spec] = opts
96
+ end
97
+
98
+ def create_indexes
99
+ self.index_meta.each do |spec, opts|
100
+ opts[:background] = true if !opts.has_key?(:background)
101
+ collection.create_index(spec, opts)
102
+ end; true
103
+ end
104
+ end # ClassMethods
105
+
106
+ def to_param
107
+ persisted? ? get("_id").to_s : nil
108
+ end
109
+
110
+ def to_key
111
+ get("_id")
112
+ end
113
+
114
+ def to_model
115
+ self
116
+ end
117
+
118
+ def persisted?
119
+ @persisted == true
120
+ #!get("_id").nil?
121
+ end
122
+
123
+ def collection
124
+ self.class.collection
125
+ end
126
+
127
+ def insert(opts={})
128
+ _run_insert_callbacks do
129
+ if persisted?
130
+ raise AlreadyInsertedError, "document has already been inserted"
131
+ end
132
+ unless valid?
133
+ if opts[:safe] == true
134
+ raise Mongoo::NotValidError, "document contains errors"
135
+ else
136
+ return false
137
+ end
138
+ end
139
+ ret = self.collection.insert(mongohash.deep_clone, opts)
140
+ unless ret.is_a?(BSON::ObjectId)
141
+ raise InsertError, "not an object: #{ret.inspect}"
142
+ end
143
+ set("_id", ret)
144
+ @persisted = true
145
+ set_persisted_mongohash(mongohash.deep_clone)
146
+ ret
147
+ end
148
+ end
149
+
150
+ def insert!(opts={})
151
+ insert(opts.merge(:safe => true))
152
+ end
153
+
154
+ def update(opts={})
155
+ _run_update_callbacks do
156
+ unless persisted?
157
+ raise NotInsertedError, "document must be inserted before being updated"
158
+ end
159
+ unless valid?
160
+ if opts[:safe] == true
161
+ raise Mongoo::NotValidError, "document contains errors"
162
+ else
163
+ return false
164
+ end
165
+ end
166
+ opts[:only_if_current] = true unless opts.has_key?(:only_if_current)
167
+ opts[:safe] = true if !opts.has_key?(:safe) && opts[:only_if_current] == true
168
+ update_hash = build_update_hash(self.changelog)
169
+ return true if update_hash.empty?
170
+ update_query_hash = build_update_query_hash(persisted_mongohash.to_key_value, self.changelog)
171
+ if Mongoo.verbose_debug
172
+ puts "\n* update_query_hash: #{update_query_hash.merge({"_id" => get("_id")}).inspect}\n update_hash: #{update_hash.inspect}\n opts: #{opts.inspect}\n"
173
+ end
174
+ ret = self.collection.update(update_query_hash.merge({"_id" => get("_id")}), update_hash, opts)
175
+ if !ret.is_a?(Hash) || (ret["updatedExisting"] && ret["n"] == 1)
176
+ set_persisted_mongohash(mongohash.deep_clone)
177
+ @persisted = true
178
+ true
179
+ else
180
+ if opts[:only_if_current]
181
+ raise StaleUpdateError, ret.inspect
182
+ else
183
+ raise UpdateError, ret.inspect
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def update!(opts={})
190
+ update(opts.merge(:safe => true))
191
+ end
192
+
193
+ def destroyed?
194
+ @destroyed != nil
195
+ end
196
+
197
+ def new_record?
198
+ !persisted?
199
+ end
200
+
201
+ def remove(opts={})
202
+ _run_remove_callbacks do
203
+ unless persisted?
204
+ raise NotInsertedError, "document must be inserted before it can be removed"
205
+ end
206
+ ret = self.collection.remove({"_id" => get("_id")}, opts)
207
+ if !ret.is_a?(Hash) || (ret["err"] == nil && ret["n"] == 1)
208
+ @destroyed = true
209
+ @persisted = false
210
+ true
211
+ else
212
+ raise RemoveError, ret.inspect
213
+ end
214
+ end
215
+ end
216
+
217
+ def remove!(opts={})
218
+ remove(opts.merge(:safe => true))
219
+ end
220
+
221
+ def reload
222
+ init_from_hash(collection.find_one(get("_id")))
223
+ @persisted = true
224
+ set_persisted_mongohash(mongohash.deep_clone)
225
+ true
226
+ end
227
+
228
+ def build_update_hash(changelog)
229
+ update_hash = {}
230
+ changelog.each do |op, k, v|
231
+ update_hash["$#{op}"] ||= {}
232
+ update_hash["$#{op}"][k] = v
233
+ end
234
+ update_hash
235
+ end
236
+ protected :build_update_hash
237
+
238
+ def build_update_query_hash(persisted_mongohash_kv, changelog)
239
+ update_query_hash = {}
240
+ changelog.each do |op, k, v|
241
+ if persisted_val = persisted_mongohash_kv[k]
242
+ if persisted_val == []
243
+ # work around a bug where mongo won't find a doc
244
+ # using an empty array [] if an index is defined
245
+ # on that field.
246
+ persisted_val = { "$size" => 0 }
247
+ end
248
+ update_query_hash[k] = persisted_val
249
+ end
250
+ end
251
+ update_query_hash
252
+ end
253
+ protected :build_update_query_hash
254
+
255
+ end
256
+ end