dm-redis-adapter 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +5 -10
- data/Rakefile +5 -18
- data/lib/dm-redis-adapter/adapter.rb +67 -32
- data/spec/dm_redis_associations_spec.rb +50 -0
- data/spec/dm_redis_spec.rb +31 -11
- data/spec/dm_redis_validations_spec.rb +1 -3
- data/spec/spec_helper.rb +6 -1
- metadata +31 -29
data/README.textile
CHANGED
@@ -1,19 +1,16 @@
|
|
1
1
|
h1. dm-redis-adapter
|
2
2
|
|
3
|
-
This is a <a href="http://datamapper.org">DataMapper</a> adapter for the <a href="http://
|
3
|
+
This is a <a href="http://datamapper.org">DataMapper</a> adapter for the <a href="http://redis.io/">Redis</a> key-value store.
|
4
4
|
|
5
|
-
Redis is a very fast key-value store with some interesting data structures added.
|
5
|
+
Redis is a very fast key-value store with some interesting data structures added, and oh so much more. You can have a key that is a SET, LIST, STRING or HASH 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 and consistent hashing on the client side. Redis makes everyone happy and has been known to cause sunshine to spontaneously break out in clouded areas.
|
6
6
|
|
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.
|
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
|
-
h1. Upgrading
|
12
|
-
|
13
|
-
> v0.1 is not compatible with data created with previous versions! The new storage schema is designed to be more VM friendly. I won't be releasing a gem version of v0.1 until a stable version of redis that includes hash support has been released.
|
14
|
-
|
15
11
|
h1. Changelog
|
16
12
|
|
13
|
+
* v0.4.0 Support for dm-core v1.1.0
|
17
14
|
* v0.3.0 Updates to support ruby 1.9.2 (thanks arbarlow!)
|
18
15
|
* v0.2.1 Fixes to sorting
|
19
16
|
* v0.1.1 Update to redis-rb v2.0.0
|
@@ -24,9 +21,7 @@ h1. Install
|
|
24
21
|
|
25
22
|
Prerequisites:
|
26
23
|
* Redis:
|
27
|
-
** <a href="http://code.google.com/p/redis/">Redis, v2.
|
28
|
-
* Gems:
|
29
|
-
** <a href="http://github.com/datamapper/dm-core/">dm-core</a> v1.0.2
|
24
|
+
** <a href="http://code.google.com/p/redis/">Redis, v2.2 series</a>
|
30
25
|
|
31
26
|
Install the dm-redis adapter:
|
32
27
|
<pre>
|
data/Rakefile
CHANGED
@@ -26,27 +26,14 @@ begin
|
|
26
26
|
gemspec.homepage = HOMEPAGE
|
27
27
|
gemspec.description = SUMMARY
|
28
28
|
gemspec.authors = AUTHORS
|
29
|
-
gemspec.add_dependency "dm-core", ">= 1.0
|
30
|
-
gemspec.add_dependency "dm-types", ">= 1.0
|
31
|
-
gemspec.add_dependency "
|
29
|
+
gemspec.add_dependency "dm-core", ">= 1.1.0"
|
30
|
+
gemspec.add_dependency "dm-types", ">= 1.1.0"
|
31
|
+
gemspec.add_dependency "hiredis", "~> 0.3.0"
|
32
|
+
gemspec.add_dependency "redis", "~> 2.2"
|
32
33
|
gemspec.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{lib,spec}/**/*")
|
33
34
|
gemspec.has_rdoc = false
|
34
35
|
gemspec.extra_rdoc_files = ["MIT-LICENSE"]
|
35
|
-
|
36
|
-
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
37
|
-
|
38
|
-
(!!) U P G R A D I N G (!!)
|
39
|
-
|
40
|
-
WAAAAAAAAAAAAAAAAAAAAAAAAIT!
|
41
|
-
|
42
|
-
Versions of dm-redis-adapter prior to v0.1
|
43
|
-
use a different method of storing properties
|
44
|
-
which means that this version of dm-redis-adapter
|
45
|
-
won't read them properly.
|
46
|
-
|
47
|
-
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
48
|
-
POST_INSTALL_MESSAGE
|
49
|
-
end
|
36
|
+
end
|
50
37
|
rescue LoadError
|
51
38
|
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
52
39
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
require 'redis/connection/hiredis'
|
1
2
|
require 'redis'
|
2
|
-
require
|
3
|
+
require 'base64'
|
3
4
|
|
4
5
|
module DataMapper
|
5
6
|
module Adapters
|
@@ -35,7 +36,8 @@ module DataMapper
|
|
35
36
|
#
|
36
37
|
# @api semipublic
|
37
38
|
def read(query)
|
38
|
-
records = records_for(query)
|
39
|
+
records = records_for(query)
|
40
|
+
records.each do |record|
|
39
41
|
record_data = @redis.hgetall("#{query.model.to_s.downcase}:#{record[redis_key_for(query.model)]}")
|
40
42
|
|
41
43
|
query.fields.each do |property|
|
@@ -49,11 +51,10 @@ module DataMapper
|
|
49
51
|
# now. All other typecasting is handled by datamapper
|
50
52
|
# separately.
|
51
53
|
record[name] = [Integer, Date].include?(property.primitive) ? property.typecast( value ) : value
|
54
|
+
record
|
52
55
|
end
|
53
56
|
end
|
54
|
-
|
55
|
-
records = query.sort_records(records)
|
56
|
-
records
|
57
|
+
query.filter_records(records)
|
57
58
|
end
|
58
59
|
|
59
60
|
##
|
@@ -119,7 +120,7 @@ module DataMapper
|
|
119
120
|
properties_to_set = []
|
120
121
|
properties_to_del = []
|
121
122
|
|
122
|
-
fields = model.properties(self.name).select {|property| attributes.key?(property)
|
123
|
+
fields = model.properties(self.name).select {|property| attributes.key?(property)}
|
123
124
|
fields.each do |property|
|
124
125
|
value = attributes[property]
|
125
126
|
if value.nil?
|
@@ -148,39 +149,73 @@ module DataMapper
|
|
148
149
|
def records_for(query)
|
149
150
|
keys = []
|
150
151
|
|
151
|
-
query.conditions.
|
152
|
-
|
153
|
-
|
154
|
-
keys << {"#{redis_key_for(query.model)}" => o.value}
|
155
|
-
end
|
152
|
+
if query.conditions.nil?
|
153
|
+
@redis.smembers(key_set_for(query.model)).each do |key|
|
154
|
+
keys << {redis_key_for(query.model) => key.to_i}
|
156
155
|
end
|
157
|
-
|
158
|
-
|
156
|
+
else
|
157
|
+
query.conditions.operands.each do |operand|
|
158
|
+
if operand.is_a?(DataMapper::Query::Conditions::OrOperation)
|
159
|
+
operand.each do |op|
|
160
|
+
keys = keys + perform_query(query, op)
|
161
|
+
end
|
162
|
+
else
|
163
|
+
keys = perform_query(query, operand)
|
164
|
+
end
|
159
165
|
end
|
160
166
|
end
|
167
|
+
keys
|
168
|
+
end
|
161
169
|
|
162
|
-
|
163
|
-
|
164
|
-
|
170
|
+
##
|
171
|
+
# Find records that match have a matching value
|
172
|
+
#
|
173
|
+
# @param [DataMapper::Query] query
|
174
|
+
# The query used to locate the resources to be deleted.
|
175
|
+
#
|
176
|
+
# @param [DataMapper::Operation] the operation for the query
|
177
|
+
#
|
178
|
+
# @api private
|
179
|
+
def perform_query(query, operand)
|
180
|
+
matched_records = []
|
165
181
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
182
|
+
if operand.is_a?(DataMapper::Query::Conditions::NotOperation)
|
183
|
+
subject = operand.first.subject
|
184
|
+
value = operand.first.value
|
185
|
+
elsif operand.subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
|
186
|
+
subject = operand.subject.child_key.first
|
187
|
+
value = operand.value[operand.subject.parent_key.first.name]
|
188
|
+
else
|
189
|
+
subject = operand.subject
|
190
|
+
value = operand.value
|
191
|
+
end
|
170
192
|
|
171
|
-
|
172
|
-
|
173
|
-
end
|
193
|
+
if subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
|
194
|
+
subject = subject.child_key.first
|
174
195
|
end
|
175
196
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
197
|
+
if query.model.key.include?(subject)
|
198
|
+
if operand.is_a?(DataMapper::Query::Conditions::NotOperation)
|
199
|
+
@redis.smembers(key_set_for(query.model)).each do |key|
|
200
|
+
if operand.matches?(subject.typecast(key))
|
201
|
+
matched_records << {redis_key_for(query.model) => key}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
elsif @redis.sismember(key_set_for(query.model), value)
|
205
|
+
matched_records << {redis_key_for(query.model) => value}
|
206
|
+
end
|
207
|
+
elsif subject.index
|
208
|
+
find_indexed_matches(subject, value).each do |k|
|
209
|
+
matched_records << {redis_key_for(query.model) => k.to_i, "#{subject.name}" => value}
|
210
|
+
end
|
211
|
+
else # worst case, loop through each record and match
|
212
|
+
@redis.smembers(key_set_for(query.model)).each do |key|
|
213
|
+
if operand.matches?(subject.typecast(@redis.hget("#{query.model.to_s.downcase}:#{key}", subject.name)))
|
214
|
+
matched_records << {redis_key_for(query.model) => key.to_i}
|
215
|
+
end
|
180
216
|
end
|
181
217
|
end
|
182
|
-
|
183
|
-
keys
|
218
|
+
matched_records
|
184
219
|
end
|
185
220
|
|
186
221
|
##
|
@@ -211,10 +246,10 @@ module DataMapper
|
|
211
246
|
# Find a matching entry for a query
|
212
247
|
#
|
213
248
|
# @return [Array]
|
214
|
-
# Array of id's of all members
|
249
|
+
# Array of id's of all members for an indexed field
|
215
250
|
# @api private
|
216
|
-
def
|
217
|
-
@redis.smembers("#{
|
251
|
+
def find_indexed_matches(subject, value)
|
252
|
+
@redis.smembers("#{subject.model.to_s.downcase}:#{subject.name}:#{encode(value)}")
|
218
253
|
end
|
219
254
|
|
220
255
|
##
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DataMapper::Adapters::RedisAdapter do
|
4
|
+
before(:all) do
|
5
|
+
@adapter = DataMapper.setup(:default, {
|
6
|
+
:adapter => "redis",
|
7
|
+
:db => 15
|
8
|
+
})
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should allow has n :through" do
|
12
|
+
class Book
|
13
|
+
include DataMapper::Resource
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
property :name, String
|
17
|
+
|
18
|
+
has n, :book_tags
|
19
|
+
has n, :tags, :through => :book_tags
|
20
|
+
end
|
21
|
+
|
22
|
+
class Tag
|
23
|
+
include DataMapper::Resource
|
24
|
+
|
25
|
+
property :id, Serial
|
26
|
+
property :name, String
|
27
|
+
|
28
|
+
has n, :book_tags
|
29
|
+
has n, :books, :through => :book_tags
|
30
|
+
end
|
31
|
+
|
32
|
+
class BookTag
|
33
|
+
include DataMapper::Resource
|
34
|
+
|
35
|
+
property :id, Serial
|
36
|
+
|
37
|
+
belongs_to :book
|
38
|
+
belongs_to :tag
|
39
|
+
end
|
40
|
+
|
41
|
+
b = Book.create(:name => "Harry Potter")
|
42
|
+
t = Tag.create(:name => "fiction")
|
43
|
+
|
44
|
+
b.tags << t
|
45
|
+
b.save
|
46
|
+
|
47
|
+
b2 = Book.get(b.id)
|
48
|
+
b2.tags.should == [t]
|
49
|
+
end
|
50
|
+
end
|
data/spec/dm_redis_spec.rb
CHANGED
@@ -1,12 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
require 'dm-core'
|
4
|
-
require 'dm-core/spec/shared/adapter_spec'
|
5
|
-
require 'dm-redis-adapter/spec/setup'
|
6
|
-
|
7
|
-
ENV['ADAPTER'] = 'redis'
|
8
|
-
ENV['ADAPTER_SUPPORTS'] = 'all'
|
9
|
-
|
10
3
|
describe DataMapper::Adapters::RedisAdapter do
|
11
4
|
before(:all) do
|
12
5
|
@adapter = DataMapper.setup(:default, {
|
@@ -14,12 +7,39 @@ describe DataMapper::Adapters::RedisAdapter do
|
|
14
7
|
:db => 15
|
15
8
|
})
|
16
9
|
@repository = DataMapper.repository(@adapter.name)
|
10
|
+
@redis = Redis.new(:db => 15)
|
17
11
|
end
|
18
12
|
|
19
13
|
it_should_behave_like 'An Adapter'
|
20
|
-
|
14
|
+
|
15
|
+
describe "adapter level tests" do
|
16
|
+
before(:all) do
|
17
|
+
class Hooloovoo
|
18
|
+
include DataMapper::Resource
|
19
|
+
|
20
|
+
property :id, Serial
|
21
|
+
property :iq, Integer
|
22
|
+
property :shade, String, :index => true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should save the id of the resource in a set" do
|
27
|
+
h = Hooloovoo.create
|
28
|
+
@redis.smembers("hooloovoo:id:all").should == [h.id.to_s]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should save indexed fields in a set by Base64 encoding the value" do
|
32
|
+
h = Hooloovoo.create(:shade => '336699')
|
33
|
+
@redis.smembers("hooloovoo:shade:MzM2Njk5").should == [h.id.to_s]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create a hash of properties for the resource" do
|
37
|
+
h = Hooloovoo.create(:iq => '4069')
|
38
|
+
@redis.hmget("hooloovoo:#{h.id}", 'iq').should == ['4069']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
21
42
|
after(:all) do
|
22
|
-
redis
|
23
|
-
redis.flushdb
|
43
|
+
@redis.flushdb
|
24
44
|
end
|
25
|
-
end
|
45
|
+
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
require File.expand_path("../spec_helper", __FILE__)
|
2
2
|
require 'dm-validations'
|
3
3
|
require 'dm-types'
|
4
|
-
require 'logger'
|
5
4
|
|
6
5
|
describe DataMapper::Adapters::RedisAdapter do
|
7
6
|
before(:all) do
|
8
7
|
@adapter = DataMapper.setup(:default, {
|
9
8
|
:adapter => "redis",
|
10
9
|
:db => 15,
|
11
|
-
# :logger => Logger.new(STDOUT)
|
12
10
|
})
|
13
11
|
end
|
14
12
|
|
@@ -117,7 +115,7 @@ describe DataMapper::Adapters::RedisAdapter do
|
|
117
115
|
petey = Blackguard.create(:nickname => "Petey 'one-eye' McGraw")
|
118
116
|
james = Blackguard.create(:nickname => "James 'cannon-fingers' Doolittle")
|
119
117
|
Blackguard.get(petey.id).should_not be_destroyed
|
120
|
-
|
118
|
+
Blackguard.first(:nickname => "James 'cannon-fingers' Doolittle").should_not be_destroyed
|
121
119
|
end
|
122
120
|
|
123
121
|
after(:each) do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 4
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.4.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Dan Herrera
|
@@ -14,11 +14,12 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-
|
17
|
+
date: 2011-05-17 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: dm-core
|
22
|
+
prerelease: false
|
22
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
23
24
|
none: false
|
24
25
|
requirements:
|
@@ -26,14 +27,14 @@ dependencies:
|
|
26
27
|
- !ruby/object:Gem::Version
|
27
28
|
segments:
|
28
29
|
- 1
|
30
|
+
- 1
|
29
31
|
- 0
|
30
|
-
|
31
|
-
version: 1.0.2
|
32
|
+
version: 1.1.0
|
32
33
|
type: :runtime
|
33
|
-
prerelease: false
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: dm-types
|
37
|
+
prerelease: false
|
37
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
38
39
|
none: false
|
39
40
|
requirements:
|
@@ -41,27 +42,40 @@ dependencies:
|
|
41
42
|
- !ruby/object:Gem::Version
|
42
43
|
segments:
|
43
44
|
- 1
|
45
|
+
- 1
|
44
46
|
- 0
|
45
|
-
|
46
|
-
version: 1.0.2
|
47
|
+
version: 1.1.0
|
47
48
|
type: :runtime
|
48
|
-
prerelease: false
|
49
49
|
version_requirements: *id002
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: hiredis
|
52
|
+
prerelease: false
|
52
53
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
54
|
none: false
|
54
55
|
requirements:
|
55
|
-
- -
|
56
|
+
- - ~>
|
56
57
|
- !ruby/object:Gem::Version
|
57
58
|
segments:
|
58
|
-
- 2
|
59
59
|
- 0
|
60
60
|
- 3
|
61
|
-
|
61
|
+
- 0
|
62
|
+
version: 0.3.0
|
62
63
|
type: :runtime
|
63
|
-
prerelease: false
|
64
64
|
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: redis
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ~>
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 2
|
75
|
+
- 2
|
76
|
+
version: "2.2"
|
77
|
+
type: :runtime
|
78
|
+
version_requirements: *id004
|
65
79
|
description: DataMapper adapter for the Redis key-value database
|
66
80
|
email: whoahbot@gmail.com
|
67
81
|
executables: []
|
@@ -77,6 +91,7 @@ files:
|
|
77
91
|
- lib/dm-redis-adapter.rb
|
78
92
|
- lib/dm-redis-adapter/adapter.rb
|
79
93
|
- lib/dm-redis-adapter/spec/setup.rb
|
94
|
+
- spec/dm_redis_associations_spec.rb
|
80
95
|
- spec/dm_redis_spec.rb
|
81
96
|
- spec/dm_redis_validations_spec.rb
|
82
97
|
- spec/spec_helper.rb
|
@@ -84,20 +99,7 @@ has_rdoc: true
|
|
84
99
|
homepage: http://github.com/whoahbot/dm-redis-adapter
|
85
100
|
licenses: []
|
86
101
|
|
87
|
-
post_install_message:
|
88
|
-
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
89
|
-
|
90
|
-
(!!) U P G R A D I N G (!!)
|
91
|
-
|
92
|
-
WAAAAAAAAAAAAAAAAAAAAAAAAIT!
|
93
|
-
|
94
|
-
Versions of dm-redis-adapter prior to v0.1
|
95
|
-
use a different method of storing properties
|
96
|
-
which means that this version of dm-redis-adapter
|
97
|
-
won't read them properly.
|
98
|
-
|
99
|
-
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
100
|
-
|
102
|
+
post_install_message:
|
101
103
|
rdoc_options:
|
102
104
|
- --charset=UTF-8
|
103
105
|
require_paths:
|
@@ -107,7 +109,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
109
|
requirements:
|
108
110
|
- - ">="
|
109
111
|
- !ruby/object:Gem::Version
|
110
|
-
hash: -599772013771505698
|
111
112
|
segments:
|
112
113
|
- 0
|
113
114
|
version: "0"
|
@@ -127,6 +128,7 @@ signing_key:
|
|
127
128
|
specification_version: 3
|
128
129
|
summary: DataMapper adapter for the Redis key-value database
|
129
130
|
test_files:
|
131
|
+
- spec/dm_redis_associations_spec.rb
|
130
132
|
- spec/dm_redis_spec.rb
|
131
133
|
- spec/dm_redis_validations_spec.rb
|
132
134
|
- spec/spec_helper.rb
|