Skip to content

Commit

Permalink
Implements no marshal Redis data store
Browse files Browse the repository at this point in the history
  - Uses a meta data hash per user defined Hash
  • Loading branch information
patrickgombert committed Dec 12, 2012
1 parent 38267b0 commit 4c7f6e5
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 13 deletions.
21 changes: 19 additions & 2 deletions redis/README.md
Expand Up @@ -30,7 +30,24 @@ Hyperion.new_datastore(:redis, options)

`:db` A specific Database to use on the Redis server: default is `0`.

### Note
### Data Types

Hyperion::Redis will transparently pack and unpack certain Ruby types.

Ruby data types that are handled include:
* String
* Fixnum
* Float
* TrueClass
* FalseClass
* NilClass
* Array
* Hash

Keys of the Hash will be returned as symbols, however all data types that are not in the above list have .to_s invoked on them (including symbols) and that will be how they are returned from Hyperion::Redis.


### Performance

Currently, all filtering, sorts, offsets, and limits happen within ruby and can become slow as a `kind` adds more and more records. Saving, lookup by id, and deletion by id are all still completed within Redis.

Expand All @@ -40,7 +57,7 @@ Clone the master branch, build, and run all the tests:

``` bash
git clone git@github.com:mylesmegyesi/hyperion-ruby.git
cd hyperion-ruby/riak
cd hyperion-ruby/redis
bundle install
bundle exec rspec
```
Expand Down
1 change: 1 addition & 0 deletions redis/Rakefile
Expand Up @@ -4,3 +4,4 @@ task :prepare_ci do
sh 'sudo service redis-server start' do |ok, res|
end
end

3 changes: 2 additions & 1 deletion redis/hyperion-redis.gemspec
Expand Up @@ -3,7 +3,7 @@
require File.expand_path('../../config', __FILE__)

Gem::Specification.new do |gem|
gem.version = '0.0.1'
gem.version = '0.2.0'
gem.name = 'hyperion-redis'
gem.description = %q{Redis datastore for Hyperion}
gem.summary = %q{Redis datastore for Hypeiron}
Expand All @@ -14,5 +14,6 @@ Gem::Specification.new do |gem|

gem.add_dependency('hyperion-api', '0.2.0')
gem.add_dependency('redis', '3.0.2')
gem.add_dependency('json', '1.7.3')
end

92 changes: 82 additions & 10 deletions redis/lib/hyperion/redis/datastore.rb
Expand Up @@ -2,6 +2,7 @@
require 'hyperion/key'
require 'hyperion/memory/helper'
require 'redis'
require 'json'

module Hyperion
module Redis
Expand All @@ -17,12 +18,15 @@ def save(records)
end

def find_by_key(key)
deserialize(@client.get(key))
record = @client.hgetall(key)
return nil if record.empty?
metarecord = @client.hgetall(meta_key(key))
cast_record(record, metarecord)
end

def find(query)
records = find_by_kind(query.kind)
records = records.map { |record| deserialize(record) }
raw_records = find_by_kind(query.kind)
records = raw_records.map { |(record, metarecord)| cast_record(record, metarecord) }
records = Hyperion::Memory::Helper.apply_filters(query.filters, records)
records = Hyperion::Memory::Helper.apply_sorts(query.sorts, records)
records = Hyperion::Memory::Helper.apply_offset(query.offset, records)
Expand All @@ -31,7 +35,10 @@ def find(query)
end

def delete_by_key(key)
@client.del(key)
@client.multi do
@client.del(key)
@client.del(meta_key(key))
end
nil
end

Expand Down Expand Up @@ -68,23 +75,88 @@ def update(record)
end

def persist_record(record)
json_record = record.reduce({}) do |acc, (key, val)|
if val.class == Hash || val.class == Array
acc[key] = JSON.dump(val)
else
acc[key] = val
end
acc
end
key = record[:key]
@client.set(key, serialize(record))
@client.multi do
@client.hmset(key, *json_record.to_a.flatten)
@client.hmset(meta_key(key), *meta_record(record).to_a.flatten)
end
end

def find_by_kind(kind)
keys = @client.keys "#{kind}:*"
@client.multi do
keys.each { |key| @client.get key }
keys.each do |key|
@client.hgetall(key)
@client.hgetall(meta_key(key))
end
end.each_slice(2).to_a
end

def meta_record(record)
record.reduce({}) do |acc, (key, val)|
acc[key] = to_db_type(val)
acc
end
end

def serialize(record)
Marshal.dump(record)
def meta_key(key)
"__metadata__#{key}"
end

def deserialize(raw_record)
Marshal.load(raw_record)
def cast_record(record, metarecord)
record.reduce({}) do |acc, (key, value)|
type = metarecord[key]
acc[key.to_sym] = from_db_type(value, type)
acc
end
end

def to_db_type(value)
if value.class.to_s == "String"
"String"
elsif value.class.to_s == "Fixnum"
"Integer"
elsif value.class.to_s == "Float"
"Number"
elsif value.class.to_s == "TrueClass" || value.class.to_s == "FalseClass"
"Boolean"
elsif value.class.to_s == "NilClass"
"Null"
elsif value.class.to_s == "Array"
"Array"
elsif value.class.to_s == "Hash"
"Object"
else
"Any"
end
end

def from_db_type(value, type)
if type == "String"
value
elsif type == "Integer"
value.to_i
elsif type == "Number"
value.to_f
elsif type == "Boolean"
value == 'true' ? true : false
elsif type == "Null"
nil
elsif type == "Array"
JSON.load(value)
elsif type == "Object"
JSON.load(value)
elsif type == "Any"
value
end
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions redis/spec/hyperion/redis_spec.rb
Expand Up @@ -6,4 +6,21 @@
with_testable_redis_datastore

include_examples 'Datastore'

it "persists and pulls out the array type" do
record = api.save(:kind => "test", :foreign_keys => [1, 2, 3])
found_record = api.find_by_key(record[:key])
found_record[:foreign_keys].should == [1, 2, 3]
end

it "persists and pulls out the hash type" do
record = api.save(:kind => "test", :map => {"some_id" => 1, "some_other_id" => 2})
found_record = api.find_by_key(record[:key])
found_record[:map]["some_id"].should == 1
found_record[:map]["some_other_id"].should == 2
end

it "returns nil for a not found key" do
api.find_by_key("notreal").should be_nil
end
end

0 comments on commit 4c7f6e5

Please sign in to comment.