whoahbot-dm-redis-adapter 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +42 -7
- data/Rakefile +3 -2
- data/lib/dm_redis_adapter.rb +19 -13
- data/spec/dm_redis_adapter_spec.rb +2 -2
- metadata +13 -4
- data/lib/rubyredis.rb +0 -237
data/README.textile
CHANGED
@@ -1,18 +1,53 @@
|
|
1
1
|
h1. dm-redis-adapter
|
2
2
|
|
3
|
-
This is
|
3
|
+
This is a <a href="http://datamapper.org">DataMapper</a> adapter for the <a href="http://github.com/antirez/redis/">Redis</a> key-value database.
|
4
4
|
|
5
|
-
|
5
|
+
Redis is a very fast key-value store with some interesting data structures added. You can have a key that is a SET, LIST, or a STRING that is binary safe. Data structures like SET and LIST allow for even more interesting things. Redis is a fabulous and fast engine for data structures, and you can read more about it here: <a href="http://code.google.com/p/redis/">redis</a>. Redis is also a persistent data store, and can be used in large-scale environments with master-slave replication.
|
6
6
|
|
7
|
-
|
7
|
+
<a href="http://datamapper.org">DataMapper</a> is a brilliant ORM that is based on the <a href="http://www.martinfowler.com/eaaCatalog/identityMap.html">IdentityMap</a> pattern. Usage of DataMapper resembles that of ActiveRecord, the popular ORM bundled with Ruby on Rails, but with some very important differences. A quote from the DM wiki: "One row in the database should equal one object reference. Pretty simple idea. Pretty profound impact." Having an identity map allows for very efficient queries to the database, as well as interesting forms of lazy loading of attributes or associations.
|
8
8
|
|
9
|
-
|
9
|
+
Marrying DataMapper to Redis allows for schema-less models, you can add fields at any time without having to create a migration. DataMapper also allows us to store non native Redis types in the db, like Date fields.
|
10
10
|
|
11
11
|
h1. Install
|
12
12
|
|
13
13
|
Prerequisites:
|
14
|
-
* Redis
|
15
|
-
** <a href="http://
|
14
|
+
* Redis:
|
15
|
+
** <a href="http://code.google.com/p/redis/">Redis, v0.100</a>
|
16
16
|
* Gems:
|
17
|
+
** <a href="http://code.google.com/p/redis/">redis (0.0.3.4)</a>
|
17
18
|
** <a href="http://github.com/datamapper/extlib">extlib</a>, dependency for dm-core
|
18
|
-
** <a href="http://github.com/datamapper/dm-core/tree/next">dm-core</a> next branch
|
19
|
+
** <a href="http://github.com/datamapper/dm-core/tree/next">dm-core</a> next branch
|
20
|
+
|
21
|
+
I installed the redis gem from the redis .tgz file like so:
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
<code>
|
25
|
+
> tar -xvzf redis-0.100.tar.gz
|
26
|
+
> cd redis-0.100/client-libraries/ruby
|
27
|
+
> sudo rake install
|
28
|
+
</code>
|
29
|
+
</pre>
|
30
|
+
|
31
|
+
h1. Usage
|
32
|
+
|
33
|
+
Setup your adapter, define your models and properties:
|
34
|
+
|
35
|
+
<pre>
|
36
|
+
<code>
|
37
|
+
require 'dm-core'
|
38
|
+
require 'dm_redis_adapter'
|
39
|
+
|
40
|
+
DataMapper.setup(:default, {:adapter => "redis"})
|
41
|
+
|
42
|
+
class Cafe
|
43
|
+
include DataMapper::Resource
|
44
|
+
|
45
|
+
property :id, Serial
|
46
|
+
property :name, Text
|
47
|
+
end
|
48
|
+
|
49
|
+
Cafe.create(:name => "Whoahbot's Caffienitorium")
|
50
|
+
</code>
|
51
|
+
</pre>
|
52
|
+
|
53
|
+
Now you can use redis in a ORM style, and take advantage of all of the amazing things that DataMapper offers.
|
data/Rakefile
CHANGED
@@ -6,10 +6,10 @@ require 'spec/rake/spectask'
|
|
6
6
|
|
7
7
|
GEM = 'dm-redis-adapter'
|
8
8
|
GEM_NAME = 'dm-redis-adapter'
|
9
|
-
GEM_VERSION = '0.0.
|
9
|
+
GEM_VERSION = '0.0.4'
|
10
10
|
AUTHORS = ['Dan Herrera']
|
11
11
|
EMAIL = "whoahbot@gmail.com"
|
12
|
-
HOMEPAGE = "http://github.com/whoahbot/dm-redis"
|
12
|
+
HOMEPAGE = "http://github.com/whoahbot/dm-redis-adapter"
|
13
13
|
SUMMARY = "DataMapper adapter for the Redis key-value database"
|
14
14
|
|
15
15
|
spec = Gem::Specification.new do |s|
|
@@ -25,6 +25,7 @@ spec = Gem::Specification.new do |s|
|
|
25
25
|
s.homepage = HOMEPAGE
|
26
26
|
s.add_dependency "rspec"
|
27
27
|
s.add_dependency "dm-core", "0.10.0"
|
28
|
+
s.add_dependency "redis", "0.0.3.4"
|
28
29
|
s.require_path = 'lib'
|
29
30
|
s.autorequire = GEM
|
30
31
|
s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{lib,spec}/**/*")
|
data/lib/dm_redis_adapter.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'redis'
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Adapters
|
@@ -17,8 +17,9 @@ module DataMapper
|
|
17
17
|
# @api semipublic
|
18
18
|
def create(resources)
|
19
19
|
resources.each do |resource|
|
20
|
-
|
21
|
-
@redis.
|
20
|
+
# model.each {|c| c.to_s}.join(":")
|
21
|
+
initialize_identity_field(resource, @redis.incr("#{resource.model.to_s.downcase}:#{redis_key_for(resource.model)}:serial"))
|
22
|
+
@redis.set_add("#{resource.model.to_s.downcase}:#{redis_key_for(resource.model)}:all", resource.key)
|
22
23
|
end
|
23
24
|
|
24
25
|
update_attributes(resources)
|
@@ -40,13 +41,13 @@ module DataMapper
|
|
40
41
|
records = records_for(query).each do |record|
|
41
42
|
query.fields.each do |property|
|
42
43
|
next if query.model.key.include?(property.name)
|
43
|
-
record[property.name.to_s] = property.typecast(@redis["#{query.model}:#{record[redis_key_for(query.model)]}:#{property.name}"])
|
44
|
+
record[property.name.to_s] = property.typecast(@redis["#{query.model.to_s.downcase}:#{record[redis_key_for(query.model)]}:#{property.name}"])
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
records = query.match_records(records)
|
48
|
-
records = query.sort_records(records)
|
49
49
|
records = query.limit_records(records)
|
50
|
+
records = query.sort_records(records)
|
50
51
|
records
|
51
52
|
end
|
52
53
|
|
@@ -84,9 +85,9 @@ module DataMapper
|
|
84
85
|
def delete(collection)
|
85
86
|
collection.query.filter_records(records_for(collection.query)).each do |record|
|
86
87
|
collection.query.model.properties.each do |p|
|
87
|
-
@redis.delete("#{collection.query.model}:#{record[redis_key_for(collection.query.model)]}:#{p.name}")
|
88
|
+
@redis.delete("#{collection.query.model.to_s.downcase}:#{record[redis_key_for(collection.query.model)]}:#{p.name}")
|
88
89
|
end
|
89
|
-
@redis.set_delete("#{collection.query.model}:#{redis_key_for(collection.query.model)}:all", record[redis_key_for(collection.query.model)])
|
90
|
+
@redis.set_delete("#{collection.query.model.to_s.downcase}:#{redis_key_for(collection.query.model)}:all", record[redis_key_for(collection.query.model)])
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
@@ -118,7 +119,7 @@ module DataMapper
|
|
118
119
|
resources.each do |resource|
|
119
120
|
resource.attributes.each do |property, value|
|
120
121
|
next if resource.key.include?(property)
|
121
|
-
@redis["#{resource.model}:#{resource.key}:#{property}"] = value unless value.nil?
|
122
|
+
@redis["#{resource.model.to_s.downcase}:#{resource.key}:#{property}"] = value unless value.nil?
|
122
123
|
end
|
123
124
|
end
|
124
125
|
end
|
@@ -135,16 +136,21 @@ module DataMapper
|
|
135
136
|
# @api private
|
136
137
|
def records_for(query)
|
137
138
|
keys = []
|
138
|
-
query.conditions.operands.select {|o| o.is_a?(Conditions::EqualToComparison) && query.model.key.include?(o.property)}.each do |o|
|
139
|
-
if @redis.set_member?("#{query.model}:#{redis_key_for(query.model)}:all", o.value)
|
139
|
+
query.conditions.operands.select {|o| o.is_a?(DataMapper::Query::Conditions::EqualToComparison) && query.model.key.include?(o.property)}.each do |o|
|
140
|
+
if @redis.set_member?("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all", o.value)
|
140
141
|
keys << {"#{redis_key_for(query.model)}" => o.value}
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
144
|
-
#
|
145
|
+
# if query.limit
|
146
|
+
# @redis.sort("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all", :limit => [query.offset, query.limit]).each do |val|
|
147
|
+
# keys << {"#{redis_key_for(query.model)}" => val.to_i}
|
148
|
+
# end
|
149
|
+
# end
|
150
|
+
|
145
151
|
# Keys are empty, fall back and load all the values for this model
|
146
152
|
if keys.empty?
|
147
|
-
@redis.set_members("#{query.model}:#{redis_key_for(query.model)}:all").each do |val|
|
153
|
+
@redis.set_members("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all").each do |val|
|
148
154
|
keys << {"#{redis_key_for(query.model)}" => val.to_i}
|
149
155
|
end
|
150
156
|
end
|
@@ -165,7 +171,7 @@ module DataMapper
|
|
165
171
|
# @api semipublic
|
166
172
|
def initialize(name, uri_or_options)
|
167
173
|
super
|
168
|
-
@redis =
|
174
|
+
@redis = Redis.new(@options)
|
169
175
|
end
|
170
176
|
end # class RedisAdapter
|
171
177
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
2
2
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib/dm_redis_adapter'))
|
3
|
-
require
|
3
|
+
require 'redis'
|
4
4
|
|
5
5
|
require 'dm-core/spec/adapter_shared_spec'
|
6
6
|
|
@@ -13,7 +13,7 @@ describe DataMapper::Adapters::RedisAdapter do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
after(:all) do
|
16
|
-
redis =
|
16
|
+
redis = Redis.new(:db => 15)
|
17
17
|
redis.flushdb
|
18
18
|
end
|
19
19
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whoahbot-dm-redis-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Herrera
|
@@ -9,7 +9,7 @@ autorequire: dm-redis-adapter
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-06-02 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +32,16 @@ dependencies:
|
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 0.10.0
|
34
34
|
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: redis
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.0.3.4
|
44
|
+
version:
|
35
45
|
description: DataMapper adapter for the Redis key-value database
|
36
46
|
email: whoahbot@gmail.com
|
37
47
|
executables: []
|
@@ -45,11 +55,10 @@ files:
|
|
45
55
|
- README.textile
|
46
56
|
- Rakefile
|
47
57
|
- lib/dm_redis_adapter.rb
|
48
|
-
- lib/rubyredis.rb
|
49
58
|
- spec/dm_redis_adapter_spec.rb
|
50
59
|
- spec/spec_helper.rb
|
51
60
|
has_rdoc: false
|
52
|
-
homepage: http://github.com/whoahbot/dm-redis
|
61
|
+
homepage: http://github.com/whoahbot/dm-redis-adapter
|
53
62
|
post_install_message:
|
54
63
|
rdoc_options: []
|
55
64
|
|
data/lib/rubyredis.rb
DELETED
@@ -1,237 +0,0 @@
|
|
1
|
-
# RubyRedis is an alternative implementatin of Ruby client library written
|
2
|
-
# by Salvatore Sanfilippo.
|
3
|
-
#
|
4
|
-
# The aim of this library is to create an alternative client library that is
|
5
|
-
# much simpler and does not implement every command explicitly but uses
|
6
|
-
# method_missing instead.
|
7
|
-
|
8
|
-
require 'socket'
|
9
|
-
require 'set'
|
10
|
-
|
11
|
-
begin
|
12
|
-
if (RUBY_VERSION >= '1.9')
|
13
|
-
require 'timeout'
|
14
|
-
RedisTimer = Timeout
|
15
|
-
else
|
16
|
-
require 'system_timer'
|
17
|
-
RedisTimer = SystemTimer
|
18
|
-
end
|
19
|
-
rescue LoadError
|
20
|
-
RedisTimer = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
class RedisClient
|
24
|
-
BulkCommands = {
|
25
|
-
"set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true,
|
26
|
-
"lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true,
|
27
|
-
"echo"=>true, "getset"=>true, "smove"=>true
|
28
|
-
}
|
29
|
-
|
30
|
-
ConvertToBool = lambda{|r| r == 0 ? false : r}
|
31
|
-
|
32
|
-
ReplyProcessor = {
|
33
|
-
"exists" => ConvertToBool,
|
34
|
-
"sismember"=> ConvertToBool,
|
35
|
-
"sadd"=> ConvertToBool,
|
36
|
-
"srem"=> ConvertToBool,
|
37
|
-
"smove"=> ConvertToBool,
|
38
|
-
"move"=> ConvertToBool,
|
39
|
-
"setnx"=> ConvertToBool,
|
40
|
-
"del"=> ConvertToBool,
|
41
|
-
"renamenx"=> ConvertToBool,
|
42
|
-
"expire"=> ConvertToBool,
|
43
|
-
"keys" => lambda{|r| r.split(" ")},
|
44
|
-
"info" => lambda{|r|
|
45
|
-
info = {}
|
46
|
-
r.each_line {|kv|
|
47
|
-
k,v = kv.split(":",2).map{|x| x.chomp}
|
48
|
-
info[k.to_sym] = v
|
49
|
-
}
|
50
|
-
info
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
Aliases = {
|
55
|
-
"flush_db" => "flushdb",
|
56
|
-
"flush_all" => "flushall",
|
57
|
-
"last_save" => "lastsave",
|
58
|
-
"key?" => "exists",
|
59
|
-
"delete" => "del",
|
60
|
-
"randkey" => "randomkey",
|
61
|
-
"list_length" => "llen",
|
62
|
-
"push_tail" => "rpush",
|
63
|
-
"push_head" => "lpush",
|
64
|
-
"pop_tail" => "rpop",
|
65
|
-
"pop_head" => "lpop",
|
66
|
-
"list_set" => "lset",
|
67
|
-
"list_range" => "lrange",
|
68
|
-
"list_trim" => "ltrim",
|
69
|
-
"list_index" => "lindex",
|
70
|
-
"list_rm" => "lrem",
|
71
|
-
"set_add" => "sadd",
|
72
|
-
"set_delete" => "srem",
|
73
|
-
"set_count" => "scard",
|
74
|
-
"set_member?" => "sismember",
|
75
|
-
"set_members" => "smembers",
|
76
|
-
"set_intersect" => "sinter",
|
77
|
-
"set_intersect_store" => "sinterstore",
|
78
|
-
"set_inter_store" => "sinterstore",
|
79
|
-
"set_union" => "sunion",
|
80
|
-
"set_union_store" => "sunionstore",
|
81
|
-
"set_diff" => "sdiff",
|
82
|
-
"set_diff_store" => "sdiffstore",
|
83
|
-
"set_move" => "smove",
|
84
|
-
"set_unless_exists" => "setnx",
|
85
|
-
"rename_unless_exists" => "renamenx"
|
86
|
-
}
|
87
|
-
|
88
|
-
def initialize(opts={})
|
89
|
-
@host = opts[:host] || '127.0.0.1'
|
90
|
-
@port = opts[:port] || 6379
|
91
|
-
@db = opts[:db] || 0
|
92
|
-
@timeout = opts[:timeout] || 0
|
93
|
-
connect_to_server
|
94
|
-
end
|
95
|
-
|
96
|
-
def to_s
|
97
|
-
"Redis Client connected to #{@host}:#{@port} against DB #{@db}"
|
98
|
-
end
|
99
|
-
|
100
|
-
def connect_to_server
|
101
|
-
@sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout)
|
102
|
-
call_command(["select",@db]) if @db != 0
|
103
|
-
end
|
104
|
-
|
105
|
-
def connect_to(host, port, timeout=nil)
|
106
|
-
# We support connect() timeout only if system_timer is availabe
|
107
|
-
# or if we are running against Ruby >= 1.9
|
108
|
-
# Timeout reading from the socket instead will be supported anyway.
|
109
|
-
if @timeout != 0 and RedisTimer
|
110
|
-
begin
|
111
|
-
sock = TCPSocket.new(host, port, 0)
|
112
|
-
rescue Timeout::Error
|
113
|
-
@sock = nil
|
114
|
-
raise Timeout::Error, "Timeout connecting to the server"
|
115
|
-
end
|
116
|
-
else
|
117
|
-
sock = TCPSocket.new(host, port, 0)
|
118
|
-
end
|
119
|
-
|
120
|
-
# If the timeout is set we set the low level socket options in order
|
121
|
-
# to make sure a blocking read will return after the specified number
|
122
|
-
# of seconds. This hack is from memcached ruby client.
|
123
|
-
if timeout
|
124
|
-
secs = Integer(timeout)
|
125
|
-
usecs = Integer((timeout - secs) * 1_000_000)
|
126
|
-
optval = [secs, usecs].pack("l_2")
|
127
|
-
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
128
|
-
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
129
|
-
end
|
130
|
-
sock
|
131
|
-
end
|
132
|
-
|
133
|
-
def method_missing(*argv)
|
134
|
-
call_command(argv)
|
135
|
-
end
|
136
|
-
|
137
|
-
def call_command(argv)
|
138
|
-
# this wrapper to raw_call_command handle reconnection on socket
|
139
|
-
# error. We try to reconnect just one time, otherwise let the error
|
140
|
-
# araise.
|
141
|
-
connect_to_server if !@sock
|
142
|
-
begin
|
143
|
-
raw_call_command(argv)
|
144
|
-
rescue Errno::ECONNRESET
|
145
|
-
@sock.close
|
146
|
-
connect_to_server
|
147
|
-
raw_call_command(argv)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def raw_call_command(argv)
|
152
|
-
bulk = nil
|
153
|
-
argv[0] = argv[0].to_s.downcase
|
154
|
-
argv[0] = Aliases[argv[0]] if Aliases[argv[0]]
|
155
|
-
if BulkCommands[argv[0]]
|
156
|
-
bulk = argv[-1].to_s
|
157
|
-
argv[-1] = bulk.length
|
158
|
-
end
|
159
|
-
@sock.write(argv.join(" ")+"\r\n")
|
160
|
-
@sock.write(bulk+"\r\n") if bulk
|
161
|
-
|
162
|
-
# Post process the reply if needed
|
163
|
-
processor = ReplyProcessor[argv[0]]
|
164
|
-
processor ? processor.call(read_reply) : read_reply
|
165
|
-
end
|
166
|
-
|
167
|
-
def select(*args)
|
168
|
-
raise "SELECT not allowed, use the :db option when creating the object"
|
169
|
-
end
|
170
|
-
|
171
|
-
def [](key)
|
172
|
-
get(key)
|
173
|
-
end
|
174
|
-
|
175
|
-
def []=(key,value)
|
176
|
-
set(key,value)
|
177
|
-
end
|
178
|
-
|
179
|
-
def sort(key, opts={})
|
180
|
-
cmd = []
|
181
|
-
cmd << "SORT #{key}"
|
182
|
-
cmd << "BY #{opts[:by]}" if opts[:by]
|
183
|
-
cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
|
184
|
-
cmd << "#{opts[:order]}" if opts[:order]
|
185
|
-
cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
|
186
|
-
call_command(cmd)
|
187
|
-
end
|
188
|
-
|
189
|
-
def incr(key,increment=nil)
|
190
|
-
call_command(increment ? ["incrby",key,increment] : ["incr",key])
|
191
|
-
end
|
192
|
-
|
193
|
-
def decr(key,decrement=nil)
|
194
|
-
call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
|
195
|
-
end
|
196
|
-
|
197
|
-
def read_reply
|
198
|
-
# We read the first byte using read() mainly because gets() is
|
199
|
-
# immune to raw socket timeouts.
|
200
|
-
begin
|
201
|
-
rtype = @sock.read(1)
|
202
|
-
rescue Errno::EAGAIN
|
203
|
-
# We want to make sure it reconnects on the next command after the
|
204
|
-
# timeout. Otherwise the server may reply in the meantime leaving
|
205
|
-
# the protocol in a desync status.
|
206
|
-
@sock = nil
|
207
|
-
raise Errno::EAGAIN, "Timeout reading from the socket"
|
208
|
-
end
|
209
|
-
|
210
|
-
raise Errno::ECONNRESET,"Connection lost" if !rtype
|
211
|
-
line = @sock.gets
|
212
|
-
case rtype
|
213
|
-
when "-"
|
214
|
-
raise "-"+line.strip
|
215
|
-
when "+"
|
216
|
-
line.strip
|
217
|
-
when ":"
|
218
|
-
line.to_i
|
219
|
-
when "$"
|
220
|
-
bulklen = line.to_i
|
221
|
-
return nil if bulklen == -1
|
222
|
-
data = @sock.read(bulklen)
|
223
|
-
@sock.read(2) # CRLF
|
224
|
-
data
|
225
|
-
when "*"
|
226
|
-
objects = line.to_i
|
227
|
-
return nil if bulklen == -1
|
228
|
-
res = []
|
229
|
-
objects.times {
|
230
|
-
res << read_reply
|
231
|
-
}
|
232
|
-
res
|
233
|
-
else
|
234
|
-
raise "Protocol error, got '#{rtype}' as initial reply byte"
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|