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 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
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'dm-core'
3
+
4
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib/dm_redis'))
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