dm-redis-adapter 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +22 -0
- data/README.textile +54 -0
- data/Rakefile +37 -0
- data/lib/dm_redis.rb +179 -0
- data/spec/dm_redis_spec.rb +20 -0
- data/spec/spec_helper.rb +4 -0
- metadata +80 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Dan Herrera
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
h1. dm-redis-adapter
|
2
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
11
|
+
h1. Install
|
12
|
+
|
13
|
+
Prerequisites:
|
14
|
+
* Redis:
|
15
|
+
** <a href="http://code.google.com/p/redis/">Redis, v0.100</a>
|
16
|
+
* Gems:
|
17
|
+
** <a href="http://code.google.com/p/redis/">redis (0.0.3.4)</a>
|
18
|
+
** <a href="http://github.com/datamapper/extlib">extlib</a>, dependency for dm-core
|
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 'rubygems'
|
38
|
+
require 'dm-core'
|
39
|
+
require 'dm_redis'
|
40
|
+
|
41
|
+
DataMapper.setup(:default, {:adapter => "redis"})
|
42
|
+
|
43
|
+
class Cafe
|
44
|
+
include DataMapper::Resource
|
45
|
+
|
46
|
+
property :id, Serial
|
47
|
+
property :name, Text
|
48
|
+
end
|
49
|
+
|
50
|
+
Cafe.create(:name => "Whoahbot's Caffienitorium")
|
51
|
+
</code>
|
52
|
+
</pre>
|
53
|
+
|
54
|
+
Now you can use redis in a ORM style, and take advantage of all of the amazing things that DataMapper offers.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
GEM = 'dm-redis-adapter'
|
5
|
+
GEM_NAME = 'dm-redis-adapter'
|
6
|
+
AUTHORS = ['Dan Herrera']
|
7
|
+
EMAIL = "whoahbot@gmail.com"
|
8
|
+
HOMEPAGE = "http://github.com/whoahbot/dm-redis-adapter"
|
9
|
+
SUMMARY = "DataMapper adapter for the Redis key-value database"
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gemspec|
|
14
|
+
gemspec.name = GEM
|
15
|
+
gemspec.summary = SUMMARY
|
16
|
+
gemspec.email = EMAIL
|
17
|
+
gemspec.homepage = HOMEPAGE
|
18
|
+
gemspec.description = SUMMARY
|
19
|
+
gemspec.authors = AUTHORS
|
20
|
+
gemspec.add_dependency "dm-core", "0.10.0"
|
21
|
+
gemspec.add_dependency "ezmobius-redis"
|
22
|
+
gemspec.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{lib,spec}/**/*")
|
23
|
+
gemspec.has_rdoc = true
|
24
|
+
gemspec.extra_rdoc_files = ["MIT-LICENSE"]
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
task :default => :spec
|
32
|
+
|
33
|
+
desc "Run specs"
|
34
|
+
Spec::Rake::SpecTask.new do |t|
|
35
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
36
|
+
t.spec_opts = %w(-fs --color)
|
37
|
+
end
|
data/lib/dm_redis.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
Extlib::Inflection.word 'redis'
|
6
|
+
|
7
|
+
class RedisAdapter < AbstractAdapter
|
8
|
+
##
|
9
|
+
# Used by DataMapper to put records into the redis data-store: "INSERT" in SQL-speak.
|
10
|
+
# It takes an array of the resources (model instances) to be saved. Resources
|
11
|
+
# each have a key that can be used to quickly look them up later without
|
12
|
+
# searching.
|
13
|
+
#
|
14
|
+
# @param [Enumerable(Resource)] resources
|
15
|
+
# The set of resources (model instances)
|
16
|
+
#
|
17
|
+
# @api semipublic
|
18
|
+
def create(resources)
|
19
|
+
resources.each do |resource|
|
20
|
+
initialize_serial(resource, @redis.incr("#{resource.model.to_s.downcase}:#{redis_key_for(resource.model)}:serial"))
|
21
|
+
@redis.set_add("#{resource.model.to_s.downcase}:#{redis_key_for(resource.model)}:all", resource.key)
|
22
|
+
end
|
23
|
+
|
24
|
+
update_attributes(resources)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Looks up one record or a collection of records from the data-store:
|
29
|
+
# "SELECT" in SQL.
|
30
|
+
#
|
31
|
+
# @param [Query] query
|
32
|
+
# The query to be used to seach for the resources
|
33
|
+
#
|
34
|
+
# @return [Array]
|
35
|
+
# An Array of Hashes containing the key-value pairs for
|
36
|
+
# each record
|
37
|
+
#
|
38
|
+
# @api semipublic
|
39
|
+
def read(query)
|
40
|
+
records = records_for(query).each do |record|
|
41
|
+
query.fields.each do |property|
|
42
|
+
next if query.model.key.include?(property)
|
43
|
+
record[property.name.to_s] = property.typecast(@redis["#{query.model.to_s.downcase}:#{record[redis_key_for(query.model)]}:#{property.name}"])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
records = query.match_records(records)
|
48
|
+
records = query.limit_records(records)
|
49
|
+
records = query.sort_records(records)
|
50
|
+
records
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Used by DataMapper to update the attributes on existing records in the redis
|
55
|
+
# data-store: "UPDATE" in SQL-speak. It takes a hash of the attributes
|
56
|
+
# to update with, as well as a collection object that specifies which resources
|
57
|
+
# should be updated.
|
58
|
+
#
|
59
|
+
# @param [Hash] attributes
|
60
|
+
# A set of key-value pairs of the attributes to update the resources with.
|
61
|
+
# @param [DataMapper::Collection] collection
|
62
|
+
# The collection object that should be used to find the resource(s) to update.
|
63
|
+
#
|
64
|
+
# @api semipublic
|
65
|
+
def update(attributes, collection)
|
66
|
+
attributes = attributes_as_fields(attributes)
|
67
|
+
|
68
|
+
records_to_update = records_for(collection.query)
|
69
|
+
records_to_update.each { |r| r.update(attributes) }
|
70
|
+
update_attributes(collection)
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Destroys all the records matching the given query. "DELETE" in SQL.
|
75
|
+
#
|
76
|
+
# @param [DataMapper::Collection] collection
|
77
|
+
# The query used to locate the resources to be deleted.
|
78
|
+
#
|
79
|
+
# @return [Array]
|
80
|
+
# An Array of Hashes containing the key-value pairs for
|
81
|
+
# each record
|
82
|
+
#
|
83
|
+
# @api semipublic
|
84
|
+
def delete(collection)
|
85
|
+
collection.query.filter_records(records_for(collection.query)).each do |record|
|
86
|
+
collection.query.model.properties.each do |p|
|
87
|
+
@redis.delete("#{collection.query.model.to_s.downcase}:#{record[redis_key_for(collection.query.model)]}:#{p.name}")
|
88
|
+
end
|
89
|
+
@redis.set_delete("#{collection.query.model.to_s.downcase}:#{redis_key_for(collection.query.model)}:all", record[redis_key_for(collection.query.model)])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
##
|
96
|
+
# Creates a string representation for the keys in a given model
|
97
|
+
#
|
98
|
+
# @param [DataMapper::Model] model
|
99
|
+
# The query used to locate the resources to be deleted.
|
100
|
+
#
|
101
|
+
# @return [Array]
|
102
|
+
# An Array of Hashes containing the key-value pairs for
|
103
|
+
# each record
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
def redis_key_for(model)
|
107
|
+
model.key.collect {|k| k.name}.join(":")
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Saves each key value pair to the redis data store
|
112
|
+
#
|
113
|
+
# @param [Array] resources
|
114
|
+
# An array of resources to save
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def update_attributes(resources)
|
118
|
+
resources.each do |resource|
|
119
|
+
resource.attributes.each do |property, value|
|
120
|
+
next if resource.key.include?(property)
|
121
|
+
@redis["#{resource.model.to_s.downcase}:#{resource.key}:#{property}"] = value unless value.nil?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Retrieves records for a particular model.
|
128
|
+
#
|
129
|
+
# @param [DataMapper::Query] query
|
130
|
+
# The query used to locate the resources
|
131
|
+
#
|
132
|
+
# @return [Array]
|
133
|
+
# An array of hashes of all of the records for a particular model
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
def records_for(query)
|
137
|
+
keys = []
|
138
|
+
query.conditions.operands.select {|o| o.is_a?(DataMapper::Query::Conditions::EqualToComparison) && query.model.key.include?(o.subject)}.each do |o|
|
139
|
+
if @redis.set_member?("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all", o.value)
|
140
|
+
keys << {"#{redis_key_for(query.model)}" => o.value}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# if query.limit
|
145
|
+
# @redis.sort("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all", :limit => [query.offset, query.limit]).each do |val|
|
146
|
+
# keys << {"#{redis_key_for(query.model)}" => val.to_i}
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
|
150
|
+
# Keys are empty, fall back and load all the values for this model
|
151
|
+
if keys.empty?
|
152
|
+
@redis.set_members("#{query.model.to_s.downcase}:#{redis_key_for(query.model)}:all").each do |val|
|
153
|
+
keys << {"#{redis_key_for(query.model)}" => val.to_i}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
keys
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Make a new instance of the adapter. The @redis ivar is the 'data-store'
|
162
|
+
# for this adapter.
|
163
|
+
#
|
164
|
+
# @param [String, Symbol] name
|
165
|
+
# The name of the Repository using this adapter.
|
166
|
+
# @param [String, Hash] uri_or_options
|
167
|
+
# The connection uri string, or a hash of options to set up
|
168
|
+
# the adapter
|
169
|
+
#
|
170
|
+
# @api semipublic
|
171
|
+
def initialize(name, uri_or_options)
|
172
|
+
super
|
173
|
+
@redis = Redis.new(@options)
|
174
|
+
end
|
175
|
+
end # class RedisAdapter
|
176
|
+
|
177
|
+
const_added(:RedisAdapter)
|
178
|
+
end # module Adapters
|
179
|
+
end # module DataMapper
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
require 'dm-core/spec/adapter_shared_spec'
|
5
|
+
|
6
|
+
describe DataMapper::Adapters::RedisAdapter do
|
7
|
+
before(:all) do
|
8
|
+
@adapter = DataMapper.setup(:default, {
|
9
|
+
:adapter => "redis",
|
10
|
+
:db => 15
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:all) do
|
15
|
+
redis = Redis.new(:db => 15)
|
16
|
+
redis.flush_db
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like 'An Adapter'
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-redis-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Herrera
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-15 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.10.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ezmobius-redis
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: DataMapper adapter for the Redis key-value database
|
36
|
+
email: whoahbot@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- MIT-LICENSE
|
43
|
+
files:
|
44
|
+
- MIT-LICENSE
|
45
|
+
- README.textile
|
46
|
+
- Rakefile
|
47
|
+
- lib/dm_redis.rb
|
48
|
+
- spec/dm_redis_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/whoahbot/dm-redis-adapter
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --charset=UTF-8
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.3
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: DataMapper adapter for the Redis key-value database
|
78
|
+
test_files:
|
79
|
+
- spec/dm_redis_spec.rb
|
80
|
+
- spec/spec_helper.rb
|