sinatra-redis 0.1.0
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/README.md +48 -0
- data/lib/sinatra/redis.rb +38 -0
- data/lib/sinatra/redis/rubyredis.rb +243 -0
- data/sinatra-redis.gemspec +33 -0
- metadata +70 -0
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
Sinatra Redis Extension
|
2
|
+
========================
|
3
|
+
|
4
|
+
Extends [Sinatra](http://www.sinatrarb.com/) with an extension method for
|
5
|
+
dealing with redis databases using the rubyredis.rb client library that
|
6
|
+
comes with the redis source (pre-packaged with this library for convenience).
|
7
|
+
|
8
|
+
Install the `sinatra-redis` with rip. No gem support yet.
|
9
|
+
|
10
|
+
$ rip install git://github.com/bmizerany/sinatra-redis.git
|
11
|
+
$ vim sinatra-using-redis.rb
|
12
|
+
|
13
|
+
require 'sinatra'
|
14
|
+
require 'sinatra/redis'
|
15
|
+
|
16
|
+
# Establish the database connection; or, omit this and use the REDIS_URL
|
17
|
+
# environment variable as the connection string; or, default to redis://locahost:6379/0
|
18
|
+
#
|
19
|
+
# NOTE: The database is the integer in the path
|
20
|
+
# set :redis, 'redis://some-remote-server:1234/5'
|
21
|
+
|
22
|
+
# At this point, you can access the Redis object using the "redis" object:
|
23
|
+
puts redis.delete "foos"
|
24
|
+
puts redis.rpush "foos", "redis"
|
25
|
+
puts redis.rpush "foos", "is"
|
26
|
+
puts redis.rpush "foos", "sweet!"
|
27
|
+
|
28
|
+
# access redis within the context of an HTTP request
|
29
|
+
get '/foos' do
|
30
|
+
@foos = redis.lrange("foos", 0, -1) # Array
|
31
|
+
@foos.inspect
|
32
|
+
end
|
33
|
+
|
34
|
+
$ ruby sinatra-using-redis.rb
|
35
|
+
|
36
|
+
### Redis Reference Material
|
37
|
+
|
38
|
+
* The [Redis Wiki](http://code.google.com/p/redis/)
|
39
|
+
|
40
|
+
* The [Redis Command Reference](http://code.google.com/p/redis/wiki/CommandReference)
|
41
|
+
|
42
|
+
* The [Redis Source](http://github.com/antirez/redis)
|
43
|
+
|
44
|
+
* Ezra's Mountain West Ruby Conf '09 [Talk](http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html)
|
45
|
+
|
46
|
+
### NOTE about the rip-off
|
47
|
+
|
48
|
+
This Code and README.md is a heavy adaption of [rtomayko's sinatra-sequel](http://github.com/rtomayko/sinatra-sequel/)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'sinatra/redis/rubyredis'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module RedisHelper
|
6
|
+
def redis
|
7
|
+
options.redis
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module RedisExtension
|
12
|
+
def redis=(url)
|
13
|
+
@redis = nil
|
14
|
+
set :redis_url, url
|
15
|
+
redis
|
16
|
+
end
|
17
|
+
|
18
|
+
def redis
|
19
|
+
url = URI(redis_url)
|
20
|
+
@redis ||=
|
21
|
+
RedisClient.new(
|
22
|
+
:host => url.host,
|
23
|
+
:port => url.port,
|
24
|
+
:db => url.path[1..-1],
|
25
|
+
:pass => url.password
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def self.registered(app)
|
32
|
+
app.set :redis_url, ENV['REDIS_URL'] || "redis://127.0.0.1:6379/0"
|
33
|
+
app.helpers RedisHelper
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
register RedisExtension
|
38
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# NOTE: This library is packaged with sinatra-redis for
|
2
|
+
# convenence. It's been copied from the redis repo.
|
3
|
+
|
4
|
+
# RubyRedis is an alternative implementatin of Ruby client library written
|
5
|
+
# by Salvatore Sanfilippo.
|
6
|
+
#
|
7
|
+
# The aim of this library is to create an alternative client library that is
|
8
|
+
# much simpler and does not implement every command explicitly but uses
|
9
|
+
# method_missing instead.
|
10
|
+
|
11
|
+
require 'socket'
|
12
|
+
require 'set'
|
13
|
+
|
14
|
+
begin
|
15
|
+
if (RUBY_VERSION >= '1.9')
|
16
|
+
require 'timeout'
|
17
|
+
RedisTimer = Timeout
|
18
|
+
else
|
19
|
+
require 'system_timer'
|
20
|
+
RedisTimer = SystemTimer
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
RedisTimer = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
class RedisClient
|
27
|
+
BulkCommands = {
|
28
|
+
"set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true,
|
29
|
+
"lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true,
|
30
|
+
"echo"=>true, "getset"=>true, "smove"=>true
|
31
|
+
}
|
32
|
+
|
33
|
+
ConvertToBool = lambda{|r| r == 0 ? false : r}
|
34
|
+
|
35
|
+
ReplyProcessor = {
|
36
|
+
"exists" => ConvertToBool,
|
37
|
+
"sismember"=> ConvertToBool,
|
38
|
+
"sadd"=> ConvertToBool,
|
39
|
+
"srem"=> ConvertToBool,
|
40
|
+
"smove"=> ConvertToBool,
|
41
|
+
"move"=> ConvertToBool,
|
42
|
+
"setnx"=> ConvertToBool,
|
43
|
+
"del"=> ConvertToBool,
|
44
|
+
"renamenx"=> ConvertToBool,
|
45
|
+
"expire"=> ConvertToBool,
|
46
|
+
"keys" => lambda{|r| r.split(" ")},
|
47
|
+
"info" => lambda{|r|
|
48
|
+
info = {}
|
49
|
+
r.each_line {|kv|
|
50
|
+
k,v = kv.split(":",2).map{|x| x.chomp}
|
51
|
+
info[k.to_sym] = v
|
52
|
+
}
|
53
|
+
info
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
Aliases = {
|
58
|
+
"flush_db" => "flushdb",
|
59
|
+
"flush_all" => "flushall",
|
60
|
+
"last_save" => "lastsave",
|
61
|
+
"key?" => "exists",
|
62
|
+
"delete" => "del",
|
63
|
+
"randkey" => "randomkey",
|
64
|
+
"list_length" => "llen",
|
65
|
+
"push_tail" => "rpush",
|
66
|
+
"push_head" => "lpush",
|
67
|
+
"pop_tail" => "rpop",
|
68
|
+
"pop_head" => "lpop",
|
69
|
+
"list_set" => "lset",
|
70
|
+
"list_range" => "lrange",
|
71
|
+
"list_trim" => "ltrim",
|
72
|
+
"list_index" => "lindex",
|
73
|
+
"list_rm" => "lrem",
|
74
|
+
"set_add" => "sadd",
|
75
|
+
"set_delete" => "srem",
|
76
|
+
"set_count" => "scard",
|
77
|
+
"set_member?" => "sismember",
|
78
|
+
"set_members" => "smembers",
|
79
|
+
"set_intersect" => "sinter",
|
80
|
+
"set_intersect_store" => "sinterstore",
|
81
|
+
"set_inter_store" => "sinterstore",
|
82
|
+
"set_union" => "sunion",
|
83
|
+
"set_union_store" => "sunionstore",
|
84
|
+
"set_diff" => "sdiff",
|
85
|
+
"set_diff_store" => "sdiffstore",
|
86
|
+
"set_move" => "smove",
|
87
|
+
"set_unless_exists" => "setnx",
|
88
|
+
"rename_unless_exists" => "renamenx"
|
89
|
+
}
|
90
|
+
|
91
|
+
def initialize(opts={})
|
92
|
+
@host = opts[:host] || '127.0.0.1'
|
93
|
+
@port = opts[:port] || 6379
|
94
|
+
@db = opts[:db] || 0
|
95
|
+
@timeout = opts[:timeout] || 0
|
96
|
+
@pass = opts[:pass] || ""
|
97
|
+
connect_to_server
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
"Redis Client connected to #{@host}:#{@port} against DB #{@db}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def connect_to_server
|
105
|
+
@sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout)
|
106
|
+
call_command(["auth",@pass]) if !@pass.empty?
|
107
|
+
call_command(["select",@db]) if @db != 0
|
108
|
+
end
|
109
|
+
|
110
|
+
def connect_to(host, port, timeout=nil)
|
111
|
+
# We support connect() timeout only if system_timer is availabe
|
112
|
+
# or if we are running against Ruby >= 1.9
|
113
|
+
# Timeout reading from the socket instead will be supported anyway.
|
114
|
+
if @timeout != 0 and RedisTimer
|
115
|
+
begin
|
116
|
+
sock = TCPSocket.new(host, port, 0)
|
117
|
+
rescue Timeout::Error
|
118
|
+
@sock = nil
|
119
|
+
raise Timeout::Error, "Timeout connecting to the server"
|
120
|
+
end
|
121
|
+
else
|
122
|
+
sock = TCPSocket.new(host, port, 0)
|
123
|
+
end
|
124
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
125
|
+
|
126
|
+
# If the timeout is set we set the low level socket options in order
|
127
|
+
# to make sure a blocking read will return after the specified number
|
128
|
+
# of seconds. This hack is from memcached ruby client.
|
129
|
+
if timeout
|
130
|
+
secs = Integer(timeout)
|
131
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
132
|
+
optval = [secs, usecs].pack("l_2")
|
133
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
134
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
135
|
+
end
|
136
|
+
sock
|
137
|
+
end
|
138
|
+
|
139
|
+
def method_missing(*argv)
|
140
|
+
call_command(argv)
|
141
|
+
end
|
142
|
+
|
143
|
+
def call_command(argv)
|
144
|
+
# this wrapper to raw_call_command handle reconnection on socket
|
145
|
+
# error. We try to reconnect just one time, otherwise let the error
|
146
|
+
# araise.
|
147
|
+
connect_to_server if !@sock
|
148
|
+
begin
|
149
|
+
raw_call_command(argv)
|
150
|
+
rescue Errno::ECONNRESET
|
151
|
+
@sock.close
|
152
|
+
connect_to_server
|
153
|
+
raw_call_command(argv)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def raw_call_command(argv)
|
158
|
+
bulk = nil
|
159
|
+
argv[0] = argv[0].to_s.downcase
|
160
|
+
argv[0] = Aliases[argv[0]] if Aliases[argv[0]]
|
161
|
+
if BulkCommands[argv[0]] and argv.length > 1
|
162
|
+
bulk = argv[-1].to_s
|
163
|
+
argv[-1] = bulk.length
|
164
|
+
end
|
165
|
+
@sock.write(argv.join(" ")+"\r\n")
|
166
|
+
@sock.write(bulk+"\r\n") if bulk
|
167
|
+
|
168
|
+
# Post process the reply if needed
|
169
|
+
processor = ReplyProcessor[argv[0]]
|
170
|
+
processor ? processor.call(read_reply) : read_reply
|
171
|
+
end
|
172
|
+
|
173
|
+
def select(*args)
|
174
|
+
raise "SELECT not allowed, use the :db option when creating the object"
|
175
|
+
end
|
176
|
+
|
177
|
+
def [](key)
|
178
|
+
get(key)
|
179
|
+
end
|
180
|
+
|
181
|
+
def []=(key,value)
|
182
|
+
set(key,value)
|
183
|
+
end
|
184
|
+
|
185
|
+
def sort(key, opts={})
|
186
|
+
cmd = []
|
187
|
+
cmd << "SORT #{key}"
|
188
|
+
cmd << "BY #{opts[:by]}" if opts[:by]
|
189
|
+
cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
|
190
|
+
cmd << "#{opts[:order]}" if opts[:order]
|
191
|
+
cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
|
192
|
+
call_command(cmd)
|
193
|
+
end
|
194
|
+
|
195
|
+
def incr(key,increment=nil)
|
196
|
+
call_command(increment ? ["incrby",key,increment] : ["incr",key])
|
197
|
+
end
|
198
|
+
|
199
|
+
def decr(key,decrement=nil)
|
200
|
+
call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
|
201
|
+
end
|
202
|
+
|
203
|
+
def read_reply
|
204
|
+
# We read the first byte using read() mainly because gets() is
|
205
|
+
# immune to raw socket timeouts.
|
206
|
+
begin
|
207
|
+
rtype = @sock.read(1)
|
208
|
+
rescue Errno::EAGAIN
|
209
|
+
# We want to make sure it reconnects on the next command after the
|
210
|
+
# timeout. Otherwise the server may reply in the meantime leaving
|
211
|
+
# the protocol in a desync status.
|
212
|
+
@sock = nil
|
213
|
+
raise Errno::EAGAIN, "Timeout reading from the socket"
|
214
|
+
end
|
215
|
+
|
216
|
+
raise Errno::ECONNRESET,"Connection lost" if !rtype
|
217
|
+
line = @sock.gets
|
218
|
+
case rtype
|
219
|
+
when "-"
|
220
|
+
raise "-"+line.strip
|
221
|
+
when "+"
|
222
|
+
line.strip
|
223
|
+
when ":"
|
224
|
+
line.to_i
|
225
|
+
when "$"
|
226
|
+
bulklen = line.to_i
|
227
|
+
return nil if bulklen == -1
|
228
|
+
data = @sock.read(bulklen)
|
229
|
+
@sock.read(2) # CRLF
|
230
|
+
data
|
231
|
+
when "*"
|
232
|
+
objects = line.to_i
|
233
|
+
return nil if bulklen == -1
|
234
|
+
res = []
|
235
|
+
objects.times {
|
236
|
+
res << read_reply
|
237
|
+
}
|
238
|
+
res
|
239
|
+
else
|
240
|
+
raise "Protocol error, got '#{rtype}' as initial reply byte"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
|
5
|
+
s.name = 'sinatra-redis'
|
6
|
+
s.version = '0.1.0'
|
7
|
+
s.date = '2009-09-21'
|
8
|
+
|
9
|
+
s.description = "Extends Sinatra with redis helpers for instant redis use"
|
10
|
+
s.summary = s.description
|
11
|
+
|
12
|
+
s.authors = ["Blake Mizerany"]
|
13
|
+
s.email = "blake.mizerany@gmail.com"
|
14
|
+
|
15
|
+
# = MANIFEST =
|
16
|
+
s.files = %w[
|
17
|
+
README.md
|
18
|
+
lib/sinatra/redis.rb
|
19
|
+
lib/sinatra/redis/rubyredis.rb
|
20
|
+
sinatra-redis.gemspec
|
21
|
+
]
|
22
|
+
# = MANIFEST =
|
23
|
+
|
24
|
+
s.extra_rdoc_files = %w[README.md]
|
25
|
+
s.add_dependency 'sinatra', '>= 0.9.4'
|
26
|
+
|
27
|
+
s.has_rdoc = true
|
28
|
+
s.homepage = "http://github.com/rtomayko/sinatra-redis"
|
29
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Sinatra::Redis"]
|
30
|
+
s.require_paths = %w[lib]
|
31
|
+
s.rubyforge_project = 'bmizerany'
|
32
|
+
s.rubygems_version = '1.1.1'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-redis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Blake Mizerany
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-21 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.4
|
24
|
+
version:
|
25
|
+
description: Extends Sinatra with redis helpers for instant redis use
|
26
|
+
email: blake.mizerany@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.md
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/sinatra/redis.rb
|
36
|
+
- lib/sinatra/redis/rubyredis.rb
|
37
|
+
- sinatra-redis.gemspec
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/rtomayko/sinatra-redis
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --line-numbers
|
45
|
+
- --inline-source
|
46
|
+
- --title
|
47
|
+
- Sinatra::Redis
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project: bmizerany
|
65
|
+
rubygems_version: 1.3.4
|
66
|
+
signing_key:
|
67
|
+
specification_version: 2
|
68
|
+
summary: Extends Sinatra with redis helpers for instant redis use
|
69
|
+
test_files: []
|
70
|
+
|