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 CHANGED
@@ -1,18 +1,53 @@
1
1
  h1. dm-redis-adapter
2
2
 
3
- This is an experimental <a href="http://datamapper.org">DataMapper</a> adapter for the <a href="http://code.google.com/p/redis/wiki/README">Redis</a> key-value database.
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
- Please be aware that this is very alpha quality software! It is not recommended for production use yet. If you find a bug, or are using dm-redis, please let me know.
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
- h1. TODO
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
- Optimizing records for to move common queries server-side.
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, git version:
15
- ** <a href="http://github.com/antirez/redis/">Redis, git version</a>
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.3'
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}/**/*")
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'rubyredis'))
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
- initialize_identity_field(resource, @redis.incr("#{resource.model}:#{redis_key_for(resource.model)}:serial"))
21
- @redis.set_add("#{resource.model}:#{redis_key_for(resource.model)}:all", resource.key)
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
- # TODO: Implement other conditions to filter down the records retrieved
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 = RedisClient.new(@options)
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 File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib/rubyredis'))
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 = RedisClient.new(:db => 15)
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.3
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-05-26 00:00:00 -07:00
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