joffice_redis 0.1.1

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,73 @@
1
+ #encoding: utf-8
2
+ =begin
3
+ Copyright 2010 Denis Kokorin <[email protected]>
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ =end
17
+
18
+
19
+ module JOffice::Redis
20
+ class HashTable
21
+ attr_reader :redis
22
+
23
+ def initialize(id, redis)
24
+ @id, @redis, @_attributes=id, redis,{}
25
+ end
26
+
27
+ def [](key)
28
+ get(key)
29
+ end
30
+
31
+ def get(key)
32
+ @_attributes[key]||=((v=redis.get(hash_key(key))) ? Marshal.load(v) : v)
33
+ end
34
+
35
+ def []=(key, value)
36
+ set(key, value)
37
+ end
38
+
39
+ def set(key, value, expire=nil)
40
+ if value
41
+ @_attributes[key]=value unless expire
42
+ k=hash_key(key)
43
+ redis.set(k, Marshal.dump(value))
44
+ redis.expire(k, expire) if expire
45
+ else
46
+ redis.del(hash_key(key))
47
+ @_attributes.delete(key)
48
+ end
49
+ value
50
+ end
51
+
52
+ def hash_key(key)
53
+ "#{@id}:#{key}".force_encoding('ASCII-8BIT')
54
+ end
55
+
56
+ def exists?(key)
57
+ k=hash_key(key)
58
+ (@_attributes.has_key?(k) || redis.exists?(k))
59
+ end
60
+
61
+ def delete(key)
62
+ redis.del(hash_key(key))
63
+ @_attributes.delete(key)
64
+ end
65
+
66
+ class << self
67
+ def keys(id, redis)
68
+ (r=redis.keys("#{id}:*")) ? r : []
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,65 @@
1
+ #encoding: utf-8
2
+ =begin
3
+ Copyright 2010 Denis Kokorin <[email protected]>
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ =end
17
+
18
+
19
+ require 'msgpack'
20
+
21
+ module JOffice::Redis
22
+ module MarshalValue
23
+
24
+ def unbox_object(value)
25
+ value.blank? ? value : Marshal.load(value)
26
+ end
27
+ module_function :unbox_object
28
+
29
+ def box_object(value)
30
+ value.blank? ? value : Marshal.dump(value)
31
+ end
32
+ module_function :box_object
33
+
34
+ def unbox_string(value)
35
+ value.blank? ? '' : MessagePack.unpack(value).encode2utf8
36
+ end
37
+ module_function :unbox_string
38
+
39
+ def box_string(value)
40
+ (value || '').to_s.to_msgpack
41
+ end
42
+ module_function :box_string
43
+
44
+ def unbox_integer(value)
45
+ value.to_i
46
+ end
47
+ module_function :unbox_integer
48
+
49
+ def box_integer(value)
50
+ value ? value.to_s : '0'
51
+ end
52
+ module_function :box_integer
53
+
54
+ def unbox_boolean(value)
55
+ '1' == value
56
+ end
57
+ module_function :unbox_boolean
58
+
59
+ def box_boolean(value)
60
+ value ? '1' : '0'
61
+ end
62
+ module_function :box_boolean
63
+
64
+ end
65
+ end
@@ -0,0 +1,417 @@
1
+ #encoding: utf-8
2
+ =begin
3
+ Copyright 2010 Denis Kokorin <[email protected]>
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ =end
17
+
18
+
19
+ require 'fileutils'
20
+ require 'digest/sha1'
21
+
22
+ module JOffice::Redis
23
+ class ModelFactory
24
+ attr_reader :parent
25
+ attr_reader :attributes
26
+ attr_reader :attributes_hash
27
+ attr_reader :attribute_lists
28
+ attr_reader :unique_indexes
29
+ attr_accessor :prefix
30
+ attr_reader :logger
31
+ attr_reader :class_module_name
32
+ attr_reader :instance_module_name
33
+
34
+ def initialize(parent, logger)
35
+ @parent=parent
36
+ @logger=logger
37
+ @consts=[]
38
+ @static_methods=[]
39
+ @instance_methods=[]
40
+ @indexes={}
41
+ @composite_indexes={}
42
+ @static_alias=[]
43
+ @index_by_field={}
44
+ @attributes,@attributes_hash,@attribute_lists,@unique_indexes = {},{},{},{}
45
+
46
+ yield self if block_given?
47
+
48
+ #@class_name="CS#{Digest::SHA1.hexdigest(parent.name)}"
49
+ @class_name="CS#{class_table}_#{prefix}"
50
+ @class_module_name="JOffice::Redis::Metadata::#{@class_name}::ClassMethods"
51
+ @instance_module_name="JOffice::Redis::Metadata::#{@class_name}::InstanceMethods"
52
+
53
+ generate
54
+
55
+
56
+ #@buffer=["", "", "module JOffice::Redis","module Metadata","# Metadata for class #{parent.name}", "module #{@class_name}"]
57
+ #@buffer << "module ClassMethods"
58
+ @buffer = ["class << self"]
59
+ @buffer << "include JOffice::Redis::MarshalValue"
60
+ @buffer+=@consts
61
+ @buffer << ""
62
+ @buffer+=@static_methods.map{|v| " #{v}"}
63
+ @buffer<< @static_alias.join("\n")
64
+ @buffer << "end"
65
+
66
+ #@buffer << "module InstanceMethods"
67
+ @buffer << ""
68
+ @buffer+=@instance_methods.map{|v| " #{v}"}
69
+ @buffer << ""
70
+ #@buffer << "end"
71
+ #@buffer << "#{parent.name}.send(:include, InstanceMethods)"
72
+ ####@buffer << "#{parent.name}.send(:extend, JOffice::Redis::CacheModelClassMethods)"
73
+ #@buffer << "#{parent.name}.send(:extend, ClassMethods)"
74
+ #@buffer+=["end","end","end"]
75
+ parent.class_eval @buffer.join("\n")
76
+ #save_and_include
77
+ end
78
+
79
+ def save_and_include
80
+ dir=File.join(File.absolute_path(File.dirname(__FILE__)),'metadata')
81
+ FileUtils.mkdir_p(dir)
82
+
83
+ path=File.join(dir, "#{@class_name}.rb")
84
+ f=File.open(path,'w')
85
+ f.write(@buffer.join("\n"))
86
+ f.close
87
+ #require path
88
+ end
89
+
90
+ def attribute(name, options={})
91
+ unique=options[:unique]
92
+ key_name=(options[:key_name] || name)
93
+ attributes[name.to_sym]=[options[:type] || :object, unique, key_name]
94
+ unique_indexes[name]=key_name if unique
95
+ end
96
+
97
+ def attribute_list(name, options={})
98
+ attribute_lists[name]=options
99
+ end
100
+
101
+ def hash(name, options={})
102
+ attributes_hash[name]=options
103
+ end
104
+
105
+ def index(name, field)
106
+ (@indexes[name]||=[]) << field
107
+ @index_by_field[field]=name
108
+ end
109
+
110
+ def composite_index(name, *fields)
111
+ options=fields.extract_options!
112
+ @composite_indexes[name]=[fields.flatten, options]
113
+ end
114
+
115
+ def set_table_name(name)
116
+ @class_table=name
117
+ end
118
+
119
+ private
120
+ def generate_unbox_value(value_name, type)
121
+ case type
122
+ when :boolean then "('1'==#{value_name})"
123
+ when :integer then "#{value_name}.to_i"
124
+ when :string then "JOffice::Redis::MarshalValue.unbox_string(#{value_name})"
125
+ else "JOffice::Redis::MarshalValue.unbox_object(#{value_name})"
126
+ end
127
+ end
128
+
129
+ def const(name, value)
130
+ @consts << "#{name}=#{value}"
131
+ end
132
+
133
+ def method(name, args=nil, static=false, log=true, block=false)
134
+ buf=[]
135
+ buf << ("JOffice::Redis::CacheModel.logger.info('Redis[#{class_table}]'){'#{name}('+[#{args}].flatten.inspect+')'}") if logger && log
136
+ yield buf
137
+ args="#{args}, &block" if block
138
+ buf=buf.map{|v| " #{v}"}.join("\n")
139
+ signature=(args ? "#{name}(#{args})" : name)
140
+ buf=["def #{signature}\n#{buf}","end"]
141
+ buf << "public :#{name}" if static
142
+ static ? (@static_methods << buf.join("\n")) : (@instance_methods << buf.join("\n"))
143
+ end
144
+
145
+ def class_table
146
+ @class_table||[email protected]
147
+ end
148
+
149
+ def key_id_set
150
+ ":'"+((prefix ? "#{prefix}::" : '') +"id").gsub(/ /,'%20')+"'"
151
+ end
152
+
153
+ def define_db
154
+ method('db', nil, true, false) do |m|
155
+ m << "@@db||=JOffice::RedisManager.instance.open_db_em('#{class_table}')"
156
+ end
157
+
158
+ method('db=', 'value', true, false) do |m|
159
+ m << "@@db=value"
160
+ end
161
+
162
+ method('db', nil, false, false) do |m|
163
+ m << "self.class.db"
164
+ end
165
+ end
166
+
167
+ def generate_key
168
+ const("ALL_MODEL_ATTRIBUTES_KEYS",attributes.keys.inspect)
169
+ const("PREFIX", ":'#{prefix}'") unless prefix.blank?
170
+
171
+ method('all_attributes',nil, true, false) do |m|
172
+ m << "#{@class_module_name}::ALL_MODEL_ATTRIBUTES_KEYS"
173
+ end
174
+
175
+ method('all_attributes', nil, false, false) do |m|
176
+ m << "#{@class_module_name}::ALL_MODEL_ATTRIBUTES_KEYS"
177
+ end
178
+
179
+ method('key_id_set',nil, true, false) do |m|
180
+ m << ":'"+(prefix ? "#{prefix}::" : '') +"id'"
181
+ end
182
+
183
+ method('prefix',nil, true, false) do |m|
184
+ m << 'PREFIX'
185
+ end unless prefix.blank?
186
+
187
+ method('prefix', nil, false, false) do |m|
188
+ m << 'PREFIX'
189
+ end if prefix && !prefix.empty?
190
+
191
+ method('key','*args', true, false) do |m|
192
+ m << "args.#{prefix ? 'unshift(prefix).' : ''}join(':').gsub(/ /,'%20').force_encoding('ASCII-8BIT')"
193
+ end
194
+
195
+ method('keys_cache', nil, false, false) do |m|
196
+ m << "@keys_cache||={}"
197
+ end
198
+
199
+ method('key','*args', false, false) do |m|
200
+ if prefix
201
+ s='([prefix, id]+args)'
202
+ else
203
+ s='args.unshift(id)'
204
+ end
205
+ m << s+".join(':').gsub(/ /,'%20').force_encoding('ASCII-8BIT')"
206
+ end
207
+
208
+ if prefix
209
+ build_key_by_id_name='"'+prefix+':#{id}:#{name}"'
210
+ else
211
+ build_key_by_id_name='"#{id}:#{name}"'
212
+ end
213
+ build_key_by_id_name+=".gsub(/ /,'%20').force_encoding('ASCII-8BIT')"
214
+
215
+ method "key_name", "id, name", true, false do |m|
216
+ m << build_key_by_id_name
217
+ end
218
+
219
+ method "key_name", "name", false do |m|
220
+ m << "keys_cache[name]||=" + build_key_by_id_name
221
+ end
222
+
223
+ method "key2id", "key", true, false do |m|
224
+ m << (prefix ? "key[#{prefix.length+1}..key.length] if key" : "key")
225
+ end
226
+
227
+ end
228
+
229
+ def generate_attributes
230
+ attributes.each do |name, options|
231
+ unique_key_name=options[2]
232
+
233
+ method name do |m|
234
+ m << "@#{name}||="+generate_unbox_value("db.get(key_name(:#{name}))", options[0])
235
+ end
236
+
237
+ method "#{name}=","value" do |m|
238
+ m << "db.set(key_name(:#{name}),JOffice::Redis::MarshalValue.box_#{options[0]}(@#{name}=value))"
239
+ m << "db.set(self.class.key_name(:#{unique_key_name},value), id)" if options[1]
240
+ m << "db.set_add(self.class.key_name(:#{@index_by_field[name]}, value), id)" if @index_by_field.has_key?(name)
241
+ end
242
+
243
+ @static_alias << "alias :unbox_value_#{name} :unbox_#{options[0]}"
244
+
245
+ if options[1]
246
+ method "find_by_#{unique_key_name}","value, select=[]", true do |m|
247
+ m << "id=db.get(key_name(:#{unique_key_name}, value))"
248
+ if unique_key_name==name
249
+ m << "id ? new(:id=>id, :#{name}=>value).ensure_attributes(select) : nil"
250
+ else
251
+ m << "id ? new(:id=>id).ensure_attributes(select) : nil"
252
+ end
253
+ end
254
+ end
255
+
256
+ if @index_by_field.has_key?(name)
257
+ index_name=@index_by_field[name]
258
+ method "find_all_by_#{index_name}", "value, select=[]", true do |m|
259
+ m << "ids=db.set_members(key_name(:#{index_name}, value))"
260
+ m << "ids ? load_by_ids(ids, select) : []"
261
+ end
262
+
263
+ method "find_all_id_by_#{name}", "value", true do |m|
264
+ m << "db.set_members(key_name(:#{index_name}, value))"
265
+ end
266
+
267
+ method "count_by_#{name}", "value", true do |m|
268
+ m << "db.scard(key_name(:#{index_name}, value))"
269
+ end
270
+
271
+ end
272
+ end
273
+ end
274
+
275
+ def generate_hash_attributes
276
+ attributes_hash.each do |name, options|
277
+ method name do |m|
278
+ m << "@#{name}||=JOffice::Redis::HashTable.new(key_name(:'#{name}'), db)"
279
+ end
280
+ end
281
+ end
282
+
283
+ def generate_save_composite_index
284
+ method "save_composite_index" do |m|
285
+ @composite_indexes.each do |index_name, d|
286
+ fields,options=*d
287
+ if options[:unique]
288
+ m << "db.set(self.class.key(:#{index_name}, #{fields.join(',')}), id)"
289
+ else
290
+ m << "db.set_add(self.class.key(:#{index_name}, #{fields.join(',')}), id)"
291
+ end
292
+ end
293
+ end
294
+
295
+ @composite_indexes.each do |index_name, d|
296
+ fields,options=*d
297
+ fields_map=fields.map{|v| ":#{v}=>#{v}"}.join(',')
298
+ fields_sym_array=":#{fields.join(',:')}"
299
+ if options[:unique]
300
+ method "find_by_#{fields.join('_and_')}","#{fields.join(',')}, select=[]", true do |m|
301
+ m << "id=db.get(key(:#{index_name}, #{fields.join(',')}))"
302
+ m << "select=select.map{|v| v.to_sym}-[#{fields_sym_array}] if select && !select.empty?"
303
+ m << "id ? new({:id=>id, #{fields_map}}).ensure_attributes(select) : nil"
304
+ end
305
+
306
+ method "exists_by_#{fields.join('_and_')}?", fields.join(','), true do |m|
307
+ m << "db.exists(key(:#{index_name}, #{fields.join(',')}))"
308
+ end
309
+ else
310
+ method "find_all_by_#{fields.join('_and_')}", "#{fields.join(',')}, select=[]", true, true, true do |m|
311
+ m << "find_by_batch_set(key(:#{index_name}, #{fields.join(',')}), {:select=>select,:default=>{#{fields_map}}}, &block)"
312
+ end
313
+
314
+ method "exists_by_#{fields.join('_and_')}?", fields.join(','), true do |m|
315
+ m << "db.exists(key(:#{index_name}, #{fields.join(',')}))"
316
+ end
317
+
318
+ method "count_by_#{fields.join('_and_')}", fields.join(','), true do |m|
319
+ m << "db.scard(key(:#{index_name}, #{fields.join(',')}))"
320
+ end
321
+ end
322
+ end
323
+ end
324
+
325
+ def generate_clone_method
326
+ method "clone", "v, id=nil", true do |m|
327
+ m << "item=new(:id=>(id ? id : v.id))"
328
+ m << "db.bulk_set do"
329
+ attributes.each do |name, options|
330
+ m << "item.#{name}=v.#{name}"
331
+ end
332
+ m << ""
333
+ m << "item.save_custom" if parent.instance_methods.index('save_custom')
334
+ m << "item.save"
335
+ m << "db.set_add(#{key_id_set}, item.id)"
336
+ m << "end"
337
+ m << "item"
338
+ end
339
+ end
340
+
341
+ def generate_exists
342
+
343
+ method 'exists?','id', true do |m|
344
+ m << "db.sismember(#{key_id_set}, id) if id"
345
+ end
346
+
347
+ method "exists?" do |m|
348
+ m << "self.class.exists?(id)"
349
+ end
350
+ end
351
+
352
+ def generate_delete_method
353
+
354
+ method "delete" do |m|
355
+ m << "return unless exists?"
356
+ m << "key_to_delete=#{@class_module_name}::ALL_MODEL_ATTRIBUTES_KEYS.map{|name| key_name( name) }"
357
+ keys_to_ensure=[]
358
+ [email protected] if @indexes.size>0
359
+ keys_to_ensure+=@composite_indexes.values.map{|v| v[0]} if @composite_indexes.size>0
360
+ m << "ensure_attributes(#{keys_to_ensure.flatten.uniq.map{|v| v.to_sym}.inspect})" unless keys_to_ensure.empty?
361
+
362
+ @composite_indexes.each do |index_name, d|
363
+ fields,options=*d
364
+ if options[:unique]
365
+ m << "key_to_delete << self.class.key(:#{index_name}, #{fields.join(',')})"
366
+ else
367
+ m << "db.set_remove_if_empty(self.class.key(:#{index_name}, #{fields.join(',')}), id)"
368
+ end
369
+ end
370
+
371
+ if unique_indexes.size>0
372
+ unique_indexes.each do |name, index|
373
+ m << "key_to_delete << self.class.key_name(:#{index}, #{name})"
374
+ end
375
+ end
376
+ if attributes_hash.size>0
377
+ m << "key_to_delete+=(db.keys(key_name('*')) || [])"
378
+ end
379
+ m << "key_to_delete+=delete_custom" if parent.instance_methods.index('delete_custom')
380
+
381
+ @indexes.each do |index, fields|
382
+ fields.each do |field|
383
+ m << "db.set_remove_if_empty(self.class.key_name(:#{index}, #{field}), id)"
384
+ end
385
+ end
386
+
387
+ m << "db.del(key_to_delete)"
388
+ m << "db.set_remove(#{key_id_set}, id)"
389
+ end
390
+
391
+ method "delete", nil, true do |m|
392
+ m << "new(:id=>id).delete"
393
+ end
394
+
395
+ end
396
+
397
+ def generate_save
398
+ generate_save_composite_index
399
+
400
+ method "save" do |m|
401
+ #m << "db.set_add(#{key_id_set}, id)"
402
+ m << "save_composite_index" if @composite_indexes.size>0
403
+ end
404
+ end
405
+
406
+ def generate
407
+ define_db
408
+ generate_key
409
+ generate_exists
410
+ generate_attributes
411
+ generate_hash_attributes
412
+ generate_clone_method
413
+ generate_delete_method
414
+ generate_save
415
+ end
416
+ end
417
+ end
OSZAR »