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 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://github.com/antirez/redis/">Redis</a> key-value database.
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. 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.
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. 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.
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.0.x RC series</a>
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.2"
30
- gemspec.add_dependency "dm-types", ">= 1.0.2"
31
- gemspec.add_dependency "redis", ">= 2.0.3"
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
- gemspec.post_install_message = <<-POST_INSTALL_MESSAGE
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 "base64"
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).each do |record|
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
- records = query.match_records(records)
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.operands.select {|o| o.is_a?(DataMapper::Query::Conditions::EqualToComparison)}.each do |o|
152
- if query.model.key.include?(o.subject)
153
- if @redis.sismember(key_set_for(query.model), o.value)
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
- find_matches(query, o).each do |k|
158
- keys << {"#{redis_key_for(query.model)}" => k.to_i, "#{o.subject.name}" => o.value}
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
- if keys.empty? && (query.order || query.limit)
163
- params = {}
164
- params[:limit] = [query.offset, query.limit] if query.limit
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
- if query.order
167
- order = query.order.first
168
- params[:order] = order.operator.to_s
169
- end
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
- @redis.sort(key_set_for(query.model), params).each do |val|
172
- keys << {"#{redis_key_for(query.model)}" => val.to_i}
173
- end
193
+ if subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
194
+ subject = subject.child_key.first
174
195
  end
175
196
 
176
- # Keys are empty, fall back and load all the values for this model
177
- if keys.empty?
178
- @redis.smembers(key_set_for(query.model)).each do |val|
179
- keys << {"#{redis_key_for(query.model)}" => val.to_i}
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 matching the query
249
+ # Array of id's of all members for an indexed field
215
250
  # @api private
216
- def find_matches(query, operand)
217
- @redis.smembers("#{query.model.to_s.downcase}:#{operand.subject.name}:#{encode(operand.value.to_s)}")
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
@@ -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 = Redis.new(:db => 15)
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
- #Blackguard.first(:nickname => "James 'cannon-fingers' Doolittle").should_not be_destroyed
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
@@ -8,4 +8,9 @@ rescue LoadError
8
8
  Bundler.setup
9
9
  end
10
10
 
11
- require 'dm-core'
11
+ require 'dm-core'
12
+ require 'dm-core/spec/shared/adapter_spec'
13
+ require 'dm-redis-adapter/spec/setup'
14
+
15
+ ENV['ADAPTER'] = 'redis'
16
+ ENV['ADAPTER_SUPPORTS'] = 'all'
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
7
+ - 4
8
8
  - 0
9
- version: 0.3.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-03-13 00:00:00 -08:00
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
- - 2
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
- - 2
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: redis
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
- version: 2.0.3
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