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 +6 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +14 -0
- data/Rakefile +70 -0
- data/VERSION.yml +4 -0
- data/familia.gemspec +57 -0
- data/lib/familia.rb +935 -0
- metadata +137 -0
data/CHANGES.txt
ADDED
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
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
|
+
|