familia 0.5.3

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/CHANGES.txt ADDED
@@ -0,0 +1,6 @@
1
+ FAMILIA, CHANGES
2
+
3
+ #### 0.5.3 (2010-12-10) ###############################
4
+
5
+ Initial public release
6
+
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010-2011 Solutious Inc, Delano Mandelbaum
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,14 @@
1
+ # Familia - 0.5 BETA
2
+
3
+ **Organize and store ruby objects in Redis**
4
+
5
+
6
+ ## More Information
7
+
8
+ * [Codes](http://github.com/delano/familia)
9
+ * [RDocs](http://delano.github.com/familia)
10
+
11
+
12
+ ## Credits
13
+
14
+ * [Delano Mandelbaum](http://goldensword.ca)
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rake/clean"
4
+ require 'yaml'
5
+
6
+ begin
7
+ require 'hanna/rdoctask'
8
+ rescue LoadError
9
+ require 'rake/rdoctask'
10
+ end
11
+
12
+ config = YAML.load_file("VERSION.yml")
13
+ task :default => ["build"]
14
+ CLEAN.include [ 'pkg', 'doc' ]
15
+ name = "familia"
16
+
17
+ begin
18
+ require "jeweler"
19
+ Jeweler::Tasks.new do |gem|
20
+ gem.version = "#{config[:MAJOR]}.#{config[:MINOR]}.#{config[:PATCH]}"
21
+ gem.name = name
22
+ gem.rubyforge_project = gem.name
23
+ gem.summary = "Organize and store ruby objects in Redis"
24
+ gem.description = gem.summary
25
+ gem.email = "delano@solutious.com"
26
+ gem.homepage = "http://github.com/delano/familia"
27
+ gem.authors = ["Delano Mandelbaum"]
28
+ gem.add_dependency("redis", ">= 2.1.0")
29
+ gem.add_dependency("uri-redis", ">= 0.4.1")
30
+ gem.add_dependency("gibbler", ">= 0.8.4")
31
+ gem.add_dependency("storable", ">= 0.8.3")
32
+
33
+ #gem.add_development_dependency("rspec", ">= 1.2.9")
34
+ #gem.add_development_dependency("mocha", ">= 0.9.8")
35
+ end
36
+ Jeweler::GemcutterTasks.new
37
+ rescue LoadError
38
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
39
+ end
40
+
41
+
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = "#{config[:MAJOR]}.#{config[:MINOR]}.#{config[:PATCH]}.#{config[:BUILD]}"
44
+ rdoc.rdoc_dir = "doc"
45
+ rdoc.title = "#{name} #{version}"
46
+ rdoc.rdoc_files.include("README*")
47
+ rdoc.rdoc_files.include("LICENSE.txt")
48
+ rdoc.rdoc_files.include("bin/*.rb")
49
+ rdoc.rdoc_files.include("lib/**/*.rb")
50
+ end
51
+
52
+
53
+ # Rubyforge Release / Publish Tasks ==================================
54
+
55
+ #about 'Publish website to rubyforge'
56
+ task 'publish:rdoc' => 'doc/index.html' do
57
+ #sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
58
+ end
59
+
60
+ #about 'Public release to rubyforge'
61
+ task 'publish:gem' => [:package] do |t|
62
+ sh <<-end
63
+ rubyforge add_release -o Any -a CHANGES.txt -f -n README.md #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
64
+ rubyforge add_file -o Any -a CHANGES.txt -f -n README.md #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
65
+ end
66
+ end
67
+
68
+
69
+
70
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :MAJOR: 0
3
+ :MINOR: 5
4
+ :PATCH: 3
data/familia.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{familia}
8
+ s.version = "0.5.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Delano Mandelbaum"]
12
+ s.date = %q{2010-12-10}
13
+ s.description = %q{Organize and store ruby objects in Redis}
14
+ s.email = %q{delano@solutious.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "CHANGES.txt",
21
+ "LICENSE.txt",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "familia.gemspec",
26
+ "lib/familia.rb"
27
+ ]
28
+ s.homepage = %q{http://github.com/delano/familia}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubyforge_project = %q{familia}
32
+ s.rubygems_version = %q{1.3.7}
33
+ s.summary = %q{Organize and store ruby objects in Redis}
34
+
35
+ if s.respond_to? :specification_version then
36
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
37
+ s.specification_version = 3
38
+
39
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
40
+ s.add_runtime_dependency(%q<redis>, [">= 2.1.0"])
41
+ s.add_runtime_dependency(%q<uri-redis>, [">= 0.4.1"])
42
+ s.add_runtime_dependency(%q<gibbler>, [">= 0.8.4"])
43
+ s.add_runtime_dependency(%q<storable>, [">= 0.8.3"])
44
+ else
45
+ s.add_dependency(%q<redis>, [">= 2.1.0"])
46
+ s.add_dependency(%q<uri-redis>, [">= 0.4.1"])
47
+ s.add_dependency(%q<gibbler>, [">= 0.8.4"])
48
+ s.add_dependency(%q<storable>, [">= 0.8.3"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<redis>, [">= 2.1.0"])
52
+ s.add_dependency(%q<uri-redis>, [">= 0.4.1"])
53
+ s.add_dependency(%q<gibbler>, [">= 0.8.4"])
54
+ s.add_dependency(%q<storable>, [">= 0.8.3"])
55
+ end
56
+ end
57
+
data/lib/familia.rb ADDED
@@ -0,0 +1,935 @@
1
+ # encoding: utf-8
2
+ FAMILIA_LIB_HOME = File.expand_path File.dirname(__FILE__) unless defined?(FAMILIA_LIB_HOME)
3
+ require 'uri/redis'
4
+
5
+ module Familia
6
+ module VERSION
7
+ def self.to_s
8
+ load_config
9
+ [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
10
+ end
11
+ alias_method :inspect, :to_s
12
+ def self.load_config
13
+ require 'yaml'
14
+ @version ||= YAML.load_file(File.join(FAMILIA_LIB_HOME, '..', 'VERSION.yml'))
15
+ end
16
+ end
17
+ end
18
+
19
+
20
+ module Familia
21
+ include Gibbler::Complex
22
+ @secret = '1-800-AWESOME' # Should be modified via Familia.secret = ''
23
+ @clients = {}
24
+ @conf = {}
25
+ @suffix = :object.freeze
26
+ @index = :id.freeze
27
+ @apiversion = nil
28
+ @uri = URI.parse 'redis://localhost'
29
+ @debug = false.freeze
30
+ @classes = []
31
+ @delim = ':'
32
+ class << self
33
+ attr_reader :conf, :classes, :clients
34
+ attr_accessor :debug, :secret, :delim
35
+ def debug?() @debug == true end
36
+ end
37
+ class Problem < RuntimeError; end
38
+ class EmptyIndex < Problem; end
39
+ class NonUniqueKey < Problem; end
40
+ class NotConnected < Problem
41
+ attr_reader :uri
42
+ def initialize uri
43
+ @uri = uri
44
+ end
45
+ def message
46
+ "No client for #{uri.serverid}"
47
+ end
48
+ end
49
+ def Familia.uri(db=nil)
50
+ if db.nil?
51
+ @uri
52
+ else
53
+ uri = URI.parse @uri.to_s
54
+ uri.db = db
55
+ uri
56
+ end
57
+ end
58
+ def Familia.apiversion(r=nil, &blk)
59
+ if blk.nil?
60
+ @apiversion = r if r;
61
+ else
62
+ tmp = @apiversion
63
+ @apiversion = r
64
+ blk.call
65
+ @apiversion = tmp
66
+ end
67
+ @apiversion
68
+ end
69
+ def Familia.apiversion=(r) @apiversion = r; r end
70
+ def Familia.conf=(conf={})
71
+ @conf = conf
72
+ @uri = Redis.uri(@conf).freeze
73
+ connect @uri
74
+ @conf
75
+ end
76
+ def Familia.redis(uri=nil)
77
+ uri &&= URI.parse uri if String === uri
78
+ uri ||= Familia.uri
79
+ connect(uri) unless @clients[uri.serverid]
80
+ #STDERR.puts "REDIS: #{uri} #{caller[0]}" if Familia.debug?
81
+ @clients[uri.serverid]
82
+ end
83
+ def Familia.connect(uri=nil, local_conf={})
84
+ uri &&= URI.parse uri if String === uri
85
+ uri ||= Familia.uri
86
+ local_conf[:thread_safe] = true
87
+ client = Redis.new local_conf.merge(uri.conf)
88
+ Familia.trace :CONNECT, client, uri.conf.inspect, caller.first
89
+ @clients[uri.serverid] = client
90
+ end
91
+ def Familia.reconnect_all!
92
+ Familia.classes.each do |klass|
93
+ klass.redis.client.reconnect
94
+ Familia.info "#{klass} ping: #{klass.redis.ping}" if debug?
95
+ end
96
+ end
97
+ def Familia.connected?(uri=nil)
98
+ uri &&= URI.parse uri if String === uri
99
+ @clients.has_key?(uri.serverid)
100
+ end
101
+ def Familia.default_suffix(a=nil) @suffix = a if a; @suffix end
102
+ def Familia.default_suffix=(a) @suffix = a end
103
+ def Familia.index(r=nil) @index = r if r; @index end
104
+ def Familia.index=(r) @index = r; r end
105
+ def Familia.split(r) r.split(Familia.delim) end
106
+ def Familia.key *args
107
+ el = args.flatten.compact
108
+ el.unshift @apiversion unless @apiversion.nil?
109
+ el.join(Familia.delim)
110
+ end
111
+ def Familia.info *msg
112
+ STDERR.puts *msg
113
+ end
114
+ def Familia.ld *msg
115
+ info *msg if debug?
116
+ end
117
+ def Familia.trace label, redis_client, ident, context=nil
118
+ return unless Familia.debug?
119
+ info "%s (%d:%s): %s" % [label, Thread.current.object_id, redis_client.object_id, ident]
120
+ info " +-> %s" % [context].flatten[0..3].join("\n ") if context
121
+ end
122
+ def Familia.destroy keyname, uri=nil
123
+ Familia.redis(uri).del keyname
124
+ end
125
+ def Familia.get_any keyname, uri=nil
126
+ type = Familia.redis(uri).type keyname
127
+ case type
128
+ when "string"
129
+ Familia.redis(uri).get keyname
130
+ when "list"
131
+ Familia.redis(uri).lrange(keyname, 0, -1) || []
132
+ when "set"
133
+ Familia.redis(uri).smembers( keyname) || []
134
+ when "zset"
135
+ Familia.redis(uri).zrange(keyname, 0, -1) || []
136
+ when "hash"
137
+ Familia.redis(uri).hgetall(keyname) || {}
138
+ else
139
+ nil
140
+ end
141
+ end
142
+ def Familia.exists?(keyname, uri=nil)
143
+ Familia.redis(uri).exists keyname
144
+ end
145
+
146
+ def self.included(obj)
147
+ obj.send :include, Familia::InstanceMethods
148
+ obj.send :include, Gibbler::Complex
149
+ obj.extend Familia::ClassMethods
150
+ Familia.classes << obj
151
+ end
152
+
153
+ module InstanceMethods
154
+ def redisinfo
155
+ info = {
156
+ :db => self.class.db || 0,
157
+ #:uri => redisuri,
158
+ :key => key,
159
+ :type => redistype,
160
+ :ttl => realttl
161
+ }
162
+ end
163
+ def exists?
164
+ Familia.redis(self.class.uri).exists self.key
165
+ end
166
+ def destroy!(suffix=nil)
167
+ ret = Familia.redis(self.class.uri).del self.key(suffix)
168
+ Familia.trace :DELETED, Familia.redis(self.class.uri), "#{key(suffix)}: #{ret}", caller.first
169
+ ret
170
+ end
171
+ def allkeys
172
+ keynames = [key]
173
+ self.class.suffixes.each do |sfx|
174
+ keynames << key(sfx)
175
+ end
176
+ keynames
177
+ end
178
+ def key(suffix=nil)
179
+ raise EmptyIndex, self.class if index.nil? || index.empty?
180
+ if suffix.nil?
181
+ suffix = self.class.suffix.kind_of?(Proc) ?
182
+ self.class.suffix.call(self) :
183
+ self.class.suffix
184
+ end
185
+ self.class.key self.index, suffix
186
+ end
187
+ def save(force=false)
188
+ Familia.trace :SAVE, Familia.redis(self.class.uri), redisuri, caller.first
189
+ ## Don't save if there are no changes
190
+ ##return false unless force || self.gibbled? || self.gibbler_cache.nil?
191
+ preprocess if respond_to?(:preprocess)
192
+ self.update_time if self.respond_to?(:update_time)
193
+ ret = Familia.redis(self.class.uri).set self.key, self.to_json
194
+ unless self.ttl.nil? || self.ttl <= 0
195
+ Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{self.key} to #{self.ttl}"
196
+ expire(self.ttl)
197
+ end
198
+ ret == "OK"
199
+ end
200
+ def index
201
+ if @index.nil?
202
+ self.class.index.kind_of?(Proc) ?
203
+ self.class.index.call(self) :
204
+ self.send(self.class.index)
205
+ else
206
+ @index
207
+ end
208
+ end
209
+ def index=(i)
210
+ @index = i
211
+ end
212
+ def expire(ttl=nil)
213
+ ttl ||= self.class.ttl
214
+ Familia.redis(self.class.uri).expire self.key, ttl.to_i
215
+ end
216
+ def realttl
217
+ Familia.redis(self.class.uri).ttl self.key
218
+ end
219
+ def ttl=(v)
220
+ @ttl = v.to_i
221
+ end
222
+ def ttl
223
+ @ttl || self.class.ttl
224
+ end
225
+ def raw(suffix=nil)
226
+ suffix ||= :object
227
+ Familia.redis(self.class.uri).get key(suffix)
228
+ end
229
+ def redisuri(suffix=nil)
230
+ u = URI.parse self.class.uri.to_s
231
+ u.db ||= self.class.db.to_s
232
+ u.key = key(suffix)
233
+ u
234
+ end
235
+ def redistype(suffix=nil)
236
+ Familia.redis(self.class.uri).type key(suffix)
237
+ end
238
+ # Finds the shortest available unique key (lower limit of 6)
239
+ def shortid
240
+ len = 6
241
+ loop do
242
+ begin
243
+ self.class.expand(@id.shorten(len))
244
+ break
245
+ rescue Familia::NonUniqueKey
246
+ len += 1
247
+ end
248
+ end
249
+ @id.shorten(len)
250
+ end
251
+ end
252
+
253
+ module ClassMethods
254
+ def inherited(obj)
255
+ obj.db = self.db
256
+ Familia.classes << obj
257
+ super(obj)
258
+ end
259
+ def from_redisdump dump
260
+ dump
261
+ end
262
+ def float
263
+ Proc.new do |v|
264
+ v.nil? ? 0 : v.to_f
265
+ end
266
+ end
267
+ def extended(obj)
268
+ obj.db = self.db
269
+ Familia.classes << obj
270
+ end
271
+ def db(db=nil)
272
+ @db = db if db;
273
+ @db
274
+ end
275
+ def db=(db) @db = db end
276
+ def host(host=nil) @host = host if host; @host end
277
+ def host=(host) @host = host end
278
+ def port(port=nil) @port = port if port; @port end
279
+ def port=(port) @port = port end
280
+ def uri=(uri)
281
+ uri = URI.parse uri if String === uri
282
+ @uri = uri
283
+ end
284
+ def uri(uri=nil)
285
+ self.uri = uri unless uri.to_s.empty?
286
+ return @uri if @uri
287
+ @uri = URI.parse Familia.uri.to_s
288
+ @uri.db = @db if @db
289
+ Familia.connect @uri #unless Familia.connected?(@uri)
290
+ @uri
291
+ end
292
+ def redis
293
+ Familia.redis(self.uri)
294
+ end
295
+ def flushdb
296
+ Familia.info "flushing #{uri}"
297
+ redis.flushdb
298
+ end
299
+ def keys(suffix=nil)
300
+ self.redis.keys(key('*',suffix)) || []
301
+ end
302
+ def all(suffix=nil)
303
+ # objects that could not be parsed will be nil
304
+ keys(suffix).collect { |k| from_key(k) }.compact
305
+ end
306
+ def any?(filter='*')
307
+ size(filter) > 0
308
+ end
309
+ def size(filter='*')
310
+ self.redis.keys(key(filter)).compact.size
311
+ end
312
+ def suffix=(val)
313
+ suffixes << (@suffix = val)
314
+ val
315
+ end
316
+ def suffix(a=nil, &blk)
317
+ @suffix = a || blk if a || !blk.nil?
318
+ val = @suffix || Familia.default_suffix
319
+ self.suffixes << val
320
+ val
321
+ end
322
+ def prefix=(a) @prefix = a end
323
+ def prefix(a=nil) @prefix = a if a; @prefix || self.name.downcase end
324
+ def index(i=nil, &blk)
325
+ @index = i || blk if i || !blk.nil?
326
+ @index ||= Familia.index
327
+ @index
328
+ end
329
+ def suffixes
330
+ @suffixes ||= []
331
+ @suffixes.uniq!
332
+ @suffixes
333
+ end
334
+ def child(opts={})
335
+ name, klass = opts.keys.first, opts.values.first
336
+ childs[name] = klass
337
+ self.suffixes << name
338
+ define_method :"#{name}_key" do
339
+ key(name)
340
+ end
341
+ define_method :"#{name}?" do
342
+ #Familia.ld "EXISTS? #{self.class.childs[name]} #{key(name)}"
343
+ self.class.childs[name].redis.exists key(name)
344
+ end
345
+ define_method :"clear_#{name}" do
346
+ self.class.redis.del key(name)
347
+ end
348
+ define_method :"#{name}" do
349
+ #Familia.ld "#{self.class} Return child #{key(name)}"
350
+ content = self.class.redis.get key(name)
351
+ #Familia.ld "TODO: don't reload #{self.class} every time"
352
+ if !content.nil?
353
+ begin
354
+ content = self.class.childs[name].from_json content if klass != String && content.is_a?(String)
355
+ rescue => ex
356
+ msg = "Error loading #{name} for #{key}: #{ex.message}"
357
+ Familia.info "#{msg}: #{$/}#{content}"
358
+ raise Familia::Problem, msg
359
+ end
360
+ else
361
+ content = self.class.childs[name].new
362
+ end
363
+ content
364
+ end
365
+ define_method :"#{name}=" do |content|
366
+ Familia.ld "#{self.class} Modify child #{key(name)} (#{content.class})"
367
+ self.class.redis.set key(name), (content.is_a?(String) ? content : content.to_json)
368
+ content
369
+ end
370
+ end
371
+ def child?(name)
372
+ childs.has_key? :"#{name}"
373
+ end
374
+ def childs
375
+ @childs ||= {}
376
+ @childs
377
+ end
378
+ def hashes
379
+ @hashes ||= {}
380
+ @hashes
381
+ end
382
+ def hash?(name)
383
+ @hashes.has_key? :"#{name}"
384
+ end
385
+ def hash(opts={}, &blk)
386
+ if Hash === opts
387
+ name, klass = opts.keys.first, opts.values.first
388
+ else
389
+ name, klass = opts, nil
390
+ end
391
+ hashes[name] = klass
392
+ self.suffixes << name
393
+ if name.to_s.match(/s$/i)
394
+ name_plural = name.to_s.clone
395
+ name_singular = name.to_s[0..-2]
396
+ else
397
+ name_plural = "#{name}s"
398
+ name_singular = name
399
+ end
400
+ define_method :"#{name}_key" do
401
+ key(name)
402
+ end
403
+ define_method :"has_#{name}?" do |field|
404
+ self.class.redis.hexists key(name), field
405
+ end
406
+ define_method :"#{name}_size" do
407
+ self.class.redis.hlen key(name)
408
+ end
409
+ define_method :"clear_#{name}" do
410
+ self.class.redis.del key(name)
411
+ end
412
+ define_method :"#{name}_keys" do
413
+ self.class.redis.hkeys key(name)
414
+ end
415
+ define_method :"set_#{name}" do |hash|
416
+ self.class.redis.hmset key(name), *hash.to_a.flatten
417
+ end
418
+ define_method :"get_#{name}" do |*fields|
419
+ ret = self.class.redis.hmget key(name), *fields
420
+ ret.collect! { |obj| blk.call(obj) } if blk
421
+ fields.size == 1 ? ret.first : ret
422
+ end
423
+ define_method :"del_#{name}" do |field|
424
+ self.class.redis.hdel key(name), field
425
+ end
426
+ end
427
+
428
+ def sets
429
+ @sets ||= {}
430
+ @sets
431
+ end
432
+ def set?(name)
433
+ sets.has_key? :"#{name}"
434
+ end
435
+ def set(opts={})
436
+ if Hash === opts
437
+ name, klass = opts.keys.first, opts.values.first
438
+ else
439
+ name, klass = opts, nil
440
+ end
441
+ sets[name] = klass
442
+ self.suffixes << name
443
+ if name.to_s.match(/s$/i)
444
+ name_plural = name.to_s.clone
445
+ name_singular = name.to_s[0..-2]
446
+ else
447
+ name_plural = "#{name}s"
448
+ name_singular = name
449
+ end
450
+ define_method :"#{name}_key" do
451
+ key(name)
452
+ end
453
+ define_method :"#{name}_size" do
454
+ self.class.redis.scard key(name)
455
+ end
456
+ # Make the value stored at KEY identical to the given list
457
+ define_method :"#{name}_sync" do |*latest|
458
+ latest = latest.flatten.compact
459
+ # Do nothing if we're given an empty Array.
460
+ # Otherwise this would clear all current values
461
+ if latest.empty?
462
+ false
463
+ else
464
+ # Convert to a list of index values if we got the actual objects
465
+ latest = latest.collect { |obj| obj.index } if klass === latest.first
466
+ current = send("#{name_plural}raw")
467
+ added = latest-current
468
+ removed = current-latest
469
+ #Familia.info "#{self.index}: adding: #{added}"
470
+ added.each { |v| self.send("add_#{name_singular}", v) }
471
+ #Familia.info "#{self.index}: removing: #{removed}"
472
+ removed.each { |v| self.send("remove_#{name_singular}", v) }
473
+ true
474
+ end
475
+ end
476
+ define_method :"#{name}?" do
477
+ self.send(:"#{name}_size") > 0
478
+ end
479
+ define_method :"clear_#{name}" do
480
+ self.class.redis.del key(name)
481
+ end
482
+ define_method :"add_#{name_singular}" do |obj|
483
+ objid = klass === obj ? obj.index : obj
484
+ #Familia.ld "#{self.class} Add #{objid} to #{key(name)}"
485
+ self.class.redis.sadd key(name), objid
486
+ end
487
+ define_method :"remove_#{name_singular}" do |obj|
488
+ objid = klass === obj ? obj.index : obj
489
+ #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
490
+ self.class.redis.srem key(name), objid
491
+ end
492
+ # Example:
493
+ #
494
+ # list = obj.response_time 10, :score => (now-12.hours)..now
495
+ #
496
+ define_method :"#{name_plural}raw" do
497
+ list = self.class.redis.smembers(key(name)) || []
498
+ end
499
+ define_method :"#{name_plural}" do
500
+ list = send("#{name_plural}raw")
501
+ if klass.nil?
502
+ list
503
+ elsif klass.include?(Familia)
504
+ klass.multiget(*list)
505
+ elsif klass.respond_to?(:from_json)
506
+ list.collect { |str| klass.from_json(str) }
507
+ else
508
+ list
509
+ end
510
+ end
511
+ end
512
+ def zsets
513
+ @zsets ||= {}
514
+ @zsets
515
+ end
516
+ def zset?(name)
517
+ zsets.has_key? :"#{name}"
518
+ end
519
+ def zset(opts={})
520
+ if Hash === opts
521
+ name, klass = opts.keys.first, opts.values.first
522
+ else
523
+ name, klass = opts, nil
524
+ end
525
+ zsets[name] = klass
526
+ self.suffixes << name
527
+ if name.to_s.match(/s$/i)
528
+ name_plural = name.to_s.clone
529
+ name_singular = name.to_s[0..-2]
530
+ else
531
+ name_plural = "#{name}s"
532
+ name_singular = name
533
+ end
534
+ define_method :"#{name}_key" do
535
+ key(name)
536
+ end
537
+ define_method :"#{name}_size" do
538
+ self.class.redis.zcard key(name)
539
+ end
540
+ define_method :"clear_#{name}" do
541
+ self.class.redis.del key(name)
542
+ end
543
+ define_method :"#{name}?" do
544
+ self.send(:"#{name}_size") > 0
545
+ end
546
+ define_method :"add_#{name_singular}" do |score,obj|
547
+ objid = klass === obj ? obj.index : obj
548
+ #Familia.ld "#{self.class} Add #{objid} (#{score}) to #{key(name)}"
549
+ self.class.redis.zadd key(name), score, objid
550
+ end
551
+ #p "Adding: #{self}#remove_#{name_singular}"
552
+ define_method :"remove_#{name_singular}" do |obj|
553
+ objid = klass === obj ? obj.index : obj
554
+ #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
555
+ self.class.redis.zrem key(name), objid
556
+ end
557
+ # Example:
558
+ #
559
+ # list = obj.response_time 10, :score => (now-12.hours)..now
560
+ #
561
+ define_method :"#{name_plural}raw" do |*args|
562
+
563
+ count = args.first-1 unless args.empty?
564
+ count ||= -1
565
+
566
+ opts = args[1] || {}
567
+ if Range === opts[:score]
568
+ lo, hi = opts[:score].first, opts[:score].last
569
+ list = self.class.redis.zrangebyscore(key(name), lo, hi, :limit => [0, count]) || []
570
+ else
571
+ list = self.class.redis.zrange(key(name), 0, count) || []
572
+ end
573
+ end
574
+ define_method :"#{name_plural}" do |*args|
575
+ list = send("#{name_plural}raw", *args)
576
+ if klass.nil?
577
+ list
578
+ elsif klass.include?(Familia)
579
+ klass.multiget(*list)
580
+ elsif klass.respond_to?(:from_json)
581
+ list.collect { |str| klass.from_json(str) }
582
+ else
583
+ list
584
+ end
585
+ end
586
+ define_method :"#{name_plural}rev" do |*args|
587
+
588
+ count = args.first-1 unless args.empty?
589
+ count ||= -1
590
+
591
+ opts = args[1] || {}
592
+ if Range === opts[:score]
593
+ lo, hi = opts[:score].first, opts[:score].last
594
+ list = self.class.redis.zrangebyscore(key(name), lo, hi, :limit => [0, count]) || []
595
+ else
596
+ list = self.class.redis.zrevrange(key(name), 0, count) || []
597
+ end
598
+ if klass.nil?
599
+ list
600
+ elsif klass.include?(Familia)
601
+ klass.multiget(*list)
602
+ elsif klass.respond_to?(:from_json)
603
+ list.collect { |str| klass.from_json(str) }
604
+ else
605
+ list
606
+ end
607
+ end
608
+ end
609
+
610
+ def list(opts={})
611
+ if Hash === opts
612
+ name, klass = opts.keys.first, opts.values.first
613
+ else
614
+ name, klass = opts, nil
615
+ end
616
+ lists[name] = klass
617
+ self.suffixes << name
618
+ if name.to_s.match(/s$/i)
619
+ name_plural = name.to_s.clone
620
+ name_singular = name.to_s[0..-2]
621
+ else
622
+ name_plural = "#{name}s"
623
+ name_singular = name
624
+ end
625
+ define_method :"#{name}_key" do
626
+ key(name)
627
+ end
628
+ define_method :"#{name}_size" do
629
+ self.class.redis.llen key(name)
630
+ end
631
+ define_method :"clear_#{name}" do
632
+ self.class.redis.del key(name)
633
+ end
634
+ # Make the value stored at KEY identical to the given list
635
+ define_method :"#{name}_sync" do |*latest|
636
+ latest = latest.flatten.compact
637
+ # Do nothing if we're given an empty Array.
638
+ # Otherwise this would clear all current values
639
+ if latest.empty?
640
+ false
641
+ else
642
+ # Convert to a list of index values if we got the actual objects
643
+ latest = latest.collect { |obj| obj.index } if klass === latest.first
644
+ current = send("#{name_plural}raw")
645
+ added = latest-current
646
+ removed = current-latest
647
+ #Familia.info "#{self.index}: adding: #{added}"
648
+ added.each { |v| self.send("add_#{name_singular}", v) }
649
+ #Familia.info "#{self.index}: removing: #{removed}"
650
+ removed.each { |v| self.send("remove_#{name_singular}", v) }
651
+ true
652
+ end
653
+ end
654
+ define_method :"#{name}?" do
655
+ self.send(:"#{name}_size") > 0
656
+ end
657
+ define_method :"add_#{name_singular}" do |obj|
658
+ objid = klass === obj ? obj.index : obj
659
+ #Familia.ld "#{self.class} Add #{objid} to #{key(name)}"
660
+ ret = self.class.redis.rpush key(name), objid
661
+ # TODO : copy to zset and set
662
+ #unless self.ttl.nil? || self.ttl <= 0
663
+ # Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{self.key} to #{self.ttl}"
664
+ # Familia.redis(self.class.uri).expire key(name), self.ttl
665
+ #end
666
+ ret
667
+ end
668
+ define_method :"remove_#{name_singular}" do |obj|
669
+ objid = klass === obj ? obj.index : obj
670
+ #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
671
+ self.class.redis.lrem key(name), 0, objid
672
+ end
673
+ define_method :"#{name_plural}raw" do |*args|
674
+ count = args.first-1 unless args.empty?
675
+ count ||= -1
676
+ list = self.class.redis.lrange(key(name), 0, count) || []
677
+ end
678
+ define_method :"#{name_plural}" do |*args|
679
+ list = send("#{name_plural}raw", *args)
680
+ if klass.nil?
681
+ list
682
+ elsif klass.include?(Familia)
683
+ klass.multiget(*list)
684
+ elsif klass.respond_to?(:from_json)
685
+ list.collect { |str| klass.from_json(str) }
686
+ else
687
+ list
688
+ end
689
+ end
690
+ end
691
+ def lists
692
+ @lists ||= {}
693
+ @lists
694
+ end
695
+ def list?(name)
696
+ lists.has_key? :"#{name}"
697
+ end
698
+ def multiget(*ids)
699
+ ids = rawmultiget(*ids)
700
+ ids.compact.collect { |json| self.from_json(json) }.compact
701
+ end
702
+ def rawmultiget(*ids)
703
+ ids.collect! { |objid| self.key(objid) }
704
+ return [] if ids.compact.empty?
705
+ Familia.trace :MULTIGET, self.redis, "#{ids.size}: #{ids}", caller
706
+ ids = self.redis.mget *ids
707
+ end
708
+ def ttl(sec=nil)
709
+ @ttl = sec.to_i unless sec.nil?
710
+ @ttl
711
+ end
712
+ def create(*args)
713
+ me = new(*args)
714
+ raise "#{self} exists: #{me.to_json}" if me.exists?
715
+ me.save
716
+ me
717
+ end
718
+ def load_or_create(id)
719
+ if exists?(id)
720
+ from_redis(id)
721
+ else
722
+ me = new id
723
+ me.save
724
+ me
725
+ end
726
+ end
727
+ def from_key(akey)
728
+ Familia.trace :LOAD, Familia.redis(self.uri), "#{self.uri}/#{akey}", caller
729
+ return nil unless Familia.redis(self.uri).exists akey
730
+ raise Familia::Problem, "Null key" if akey.nil? || akey.empty?
731
+ run_json = Familia.redis(self.uri).get akey
732
+ if run_json.nil? || run_json.empty?
733
+ Familia.info "No content @ #{akey}"
734
+ return
735
+ end
736
+ begin
737
+ #run_json.force_encoding("ASCII-8BIT") if RUBY_VERSION >= "1.9"
738
+ obj = self.from_json(run_json)
739
+ obj
740
+ rescue => ex
741
+ STDOUT.puts "Non-fatal error parsing JSON for #{akey}: #{ex.message}"
742
+ STDOUT.puts run_json
743
+ STDERR.puts ex.backtrace
744
+ nil
745
+ end
746
+ end
747
+ def from_redis(objid, suffix=nil)
748
+ objid &&= objid.to_s
749
+ return nil if objid.nil? || objid.empty?
750
+ this_key = key(objid, suffix)
751
+ #Familia.ld "Reading key: #{this_key}"
752
+ me = from_key(this_key)
753
+ me.gibbler # prime the gibbler cache (used to check for changes)
754
+ me
755
+ end
756
+ def exists?(objid, suffix=nil)
757
+ objid &&= objid.to_s
758
+ return false if objid.nil? || objid.empty?
759
+ ret = Familia.redis(self.uri).exists key(objid, suffix)
760
+ Familia.trace :EXISTS, Familia.redis(self.uri), "#{key(objid)} #{ret}", caller.first
761
+ ret
762
+ end
763
+ def destroy!(runid, suffix=nil)
764
+ ret = Familia.redis(self.uri).del key(runid, suffix)
765
+ Familia.trace :DELETED, Familia.redis(self.uri), "#{key(runid)}: #{ret}", caller.first
766
+ ret
767
+ end
768
+ def find(suffix='*')
769
+ list = Familia.redis(self.uri).keys(key('*', suffix)) || []
770
+ end
771
+ def key(runid, suffix=nil)
772
+ suffix ||= self.suffix
773
+ runid ||= ''
774
+ runid &&= runid.to_s
775
+ str = Familia.key(prefix, runid, suffix)
776
+ str
777
+ end
778
+ def expand(short_key, suffix=nil)
779
+ suffix ||= self.suffix
780
+ expand_key = Familia.key(self.prefix, "#{short_key}*", suffix)
781
+ Familia.trace :EXPAND, Familia.redis(self.uri), expand_key, caller.first
782
+ list = Familia.redis(self.uri).keys expand_key
783
+ case list.size
784
+ when 0
785
+ nil
786
+ when 1
787
+ matches = list.first.match(/\A#{Familia.key(prefix)}\:(.+?)\:#{suffix}/) || []
788
+ matches[1]
789
+ else
790
+ raise Familia::NonUniqueKey, "Short key returned more than 1 match"
791
+ end
792
+ end
793
+ end
794
+ end
795
+
796
+ module Familia
797
+ #
798
+ # class Example
799
+ # include Familia
800
+ # field :name
801
+ # include Familia::Stamps
802
+ # end
803
+ #
804
+ module Stamps
805
+ def self.included(obj)
806
+ obj.module_eval do
807
+ field :created => Integer
808
+ field :updated => Integer
809
+ def init_stamps
810
+ now = Time.now.utc.to_i
811
+ @created ||= now
812
+ @updated ||= now
813
+ end
814
+ def created
815
+ @created ||= Time.now.utc.to_i
816
+ end
817
+ def updated
818
+ @updated ||= Time.now.utc.to_i
819
+ end
820
+ def created_age
821
+ Time.now.utc.to_i-created
822
+ end
823
+ def updated_age
824
+ Time.now.utc.to_i-updated
825
+ end
826
+ def update_time
827
+ @updated = Time.now.utc.to_i
828
+ end
829
+ def update_time!
830
+ update_time
831
+ save if respond_to? :save
832
+ @updated
833
+ end
834
+ end
835
+ end
836
+ end
837
+ module Status
838
+ def self.included(obj)
839
+ obj.module_eval do
840
+ field :status
841
+ field :message
842
+ def failure?() status? 'failure' end
843
+ def success?() status? 'success' end
844
+ def pending?() status? 'pending' end
845
+ def expired?() status? 'expired' end
846
+ def disabled?() status? 'disabled' end
847
+ def failure!(msg=nil) status! 'failure', msg end
848
+ def success!(msg=nil) status! 'success', msg end
849
+ def pending!(msg=nil) status! 'pending', msg end
850
+ def expired!(msg=nil) status! 'expired', msg end
851
+ def disabled!(msg=nil) status! 'disabled', msg end
852
+ private
853
+ def status?(s)
854
+ status.to_s == s.to_s
855
+ end
856
+ def status!(s, msg=nil)
857
+ @updated = Time.now.utc.to_f
858
+ @status, @message = s, msg
859
+ save if respond_to? :save
860
+ end
861
+ end
862
+ end
863
+ end
864
+ end
865
+
866
+ module Familia
867
+ module Tools
868
+ extend self
869
+ def move_keys(filter, source_uri, target_uri, &each_key)
870
+ if target_uri == source_uri
871
+ raise "Source and target are the same (#{target_uri})"
872
+ end
873
+ Familia.connect target_uri
874
+ source_keys = Familia.redis(source_uri).keys(filter)
875
+ puts "Moving #{source_keys.size} keys from #{source_uri} to #{target_uri} (filter: #{filter})"
876
+ source_keys.each_with_index do |key,idx|
877
+ type = Familia.redis(source_uri).type key
878
+ ttl = Familia.redis(source_uri).ttl key
879
+ if source_uri.host == target_uri.host && source_uri.port == target_uri.port
880
+ Familia.redis(source_uri).move key, target_uri.db
881
+ else
882
+ case type
883
+ when "string"
884
+ value = Familia.redis(source_uri).get key
885
+ when "list"
886
+ value = Familia.redis(source_uri).lrange key, 0, -1
887
+ when "set"
888
+ value = Familia.redis(source_uri).smembers key
889
+ else
890
+ raise Familia::Problem, "unknown key type: #{type}"
891
+ end
892
+ raise "Not implemented"
893
+ end
894
+ each_key.call(idx, type, key, ttl) unless each_key.nil?
895
+ end
896
+ end
897
+ # Use the return value from each_key as the new key name
898
+ def rename(filter, source_uri, target_uri=nil, &each_key)
899
+ target_uri ||= source_uri
900
+ move_keys filter, source_uri, target_uri if source_uri != target_uri
901
+ source_keys = Familia.redis(source_uri).keys(filter)
902
+ puts "Renaming #{source_keys.size} keys from #{source_uri} (filter: #{filter})"
903
+ source_keys.each_with_index do |key,idx|
904
+ Familia.trace :RENAME1, Familia.redis(source_uri), "#{key}", ''
905
+ type = Familia.redis(source_uri).type key
906
+ ttl = Familia.redis(source_uri).ttl key
907
+ newkey = each_key.call(idx, type, key, ttl) unless each_key.nil?
908
+ Familia.trace :RENAME2, Familia.redis(source_uri), "#{key} -> #{newkey}", caller[0]
909
+ ret = Familia.redis(source_uri).renamenx key, newkey
910
+ end
911
+ end
912
+ end
913
+ end
914
+
915
+
916
+ module Familia
917
+ module Collector
918
+ def klasses
919
+ @klasses ||= []
920
+ @klasses
921
+ end
922
+ def included(obj)
923
+ self.klasses << obj
924
+ end
925
+ end
926
+ end
927
+
928
+ class Symbol
929
+ unless method_defined?(:to_proc)
930
+ def to_proc
931
+ proc { |obj, *args| obj.send(self, *args) }
932
+ end
933
+ end
934
+ end
935
+
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: familia
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 3
10
+ version: 0.5.3
11
+ platform: ruby
12
+ authors:
13
+ - Delano Mandelbaum
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-10 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: redis
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 11
30
+ segments:
31
+ - 2
32
+ - 1
33
+ - 0
34
+ version: 2.1.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: uri-redis
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 13
46
+ segments:
47
+ - 0
48
+ - 4
49
+ - 1
50
+ version: 0.4.1
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: gibbler
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 55
62
+ segments:
63
+ - 0
64
+ - 8
65
+ - 4
66
+ version: 0.8.4
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: storable
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 57
78
+ segments:
79
+ - 0
80
+ - 8
81
+ - 3
82
+ version: 0.8.3
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ description: Organize and store ruby objects in Redis
86
+ email: delano@solutious.com
87
+ executables: []
88
+
89
+ extensions: []
90
+
91
+ extra_rdoc_files:
92
+ - LICENSE.txt
93
+ - README.rdoc
94
+ files:
95
+ - CHANGES.txt
96
+ - LICENSE.txt
97
+ - README.rdoc
98
+ - Rakefile
99
+ - VERSION.yml
100
+ - familia.gemspec
101
+ - lib/familia.rb
102
+ has_rdoc: true
103
+ homepage: http://github.com/delano/familia
104
+ licenses: []
105
+
106
+ post_install_message:
107
+ rdoc_options:
108
+ - --charset=UTF-8
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ requirements: []
130
+
131
+ rubyforge_project: familia
132
+ rubygems_version: 1.3.7
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: Organize and store ruby objects in Redis
136
+ test_files: []
137
+