mongoo 0.1.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.
@@ -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