hyperion 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +68 -0
- data/Rakefile +84 -0
- data/VERSION +1 -0
- data/benchmarks.rb +100 -0
- data/hyperion.gemspec +58 -0
- data/lib/hyperion.rb +168 -0
- data/lib/hyperion/base.rb +14 -0
- data/lib/hyperion/finders.rb +12 -0
- data/lib/hyperion/indices.rb +22 -0
- data/lib/hyperion/keys.rb +10 -0
- data/lib/hyperion/logger.rb +16 -0
- data/lib/hyperion/version.rb +14 -0
- data/spec/hyperion_spec.rb +111 -0
- data/spec/spec_helper.rb +30 -0
- metadata +82 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 adrianpike
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
= hyperion
|
2
|
+
|
3
|
+
Hyperion is a lightning fast Ruby object model backed with Redis. It was created in 48 hours to back Leatherbound.me, a ebook comparison shopping tool that was built for Rails Rumble. We withstood 15,000 uniques on Monday morning, and Hyperion stayed super quick even with tons of concurrent queries on a small Linode.
|
4
|
+
|
5
|
+
== Key features
|
6
|
+
|
7
|
+
* CRUD on Ruby classes, any attributes get stuffed into a Redis key.
|
8
|
+
* Indexing of objects on any attributes or collections of attributes.
|
9
|
+
|
10
|
+
== A quickie
|
11
|
+
|
12
|
+
adrian$ gem install hyperion
|
13
|
+
|
14
|
+
require 'hyperion'
|
15
|
+
|
16
|
+
class Person < Hyperion
|
17
|
+
# First we set up the attributes on this object.
|
18
|
+
attr_accessor :name, :email, :password, :other_amazing_info, :car_color, :state
|
19
|
+
|
20
|
+
# Let's use the email as our primary key.
|
21
|
+
# Another option would be to not define our own, in which case :id will be created & used.
|
22
|
+
hyperion_key :email
|
23
|
+
|
24
|
+
# Lets add some indexes!
|
25
|
+
# We can search for all Persons with a given name.
|
26
|
+
hyperion_index :name
|
27
|
+
|
28
|
+
# We can also search for all people with a given car color and state.
|
29
|
+
hyperion_index [:car_color, :state]
|
30
|
+
end
|
31
|
+
|
32
|
+
p1 = Person.new({:name => 'Adrian Pike', :email => 'adrian@pikeapps.com', …})
|
33
|
+
p1.save
|
34
|
+
|
35
|
+
Person.find('adrian@pikeapps.com') #=> #<Person @name="Adrian Pike", @email="adrian@pikeapps.com">
|
36
|
+
Person.find(:name => 'Adrian Pike') #=> [#<Person @name="Adrian Pike", @email="adrian@pikeapps.com">]
|
37
|
+
Person.find(:car_color => 'purple', :state => 'denial') #=> [#<Person @name="Adrian Pike", @email="adrian@pikeapps.com">]
|
38
|
+
|
39
|
+
== Requirements
|
40
|
+
|
41
|
+
* Ruby 1.9
|
42
|
+
* activesupport
|
43
|
+
* Redis
|
44
|
+
|
45
|
+
== Roadmap
|
46
|
+
|
47
|
+
* Change the indexes to use ZSETs.
|
48
|
+
* Delete. (ha!)
|
49
|
+
* Schema versioning and migrations.
|
50
|
+
* Faster faster faster! Let's utilize more of Redis' exposed data structures to make it better.
|
51
|
+
* Clever auto-sharding based on primary keyspace
|
52
|
+
* Figure out a way to auto-balance shards
|
53
|
+
* Slow full-table searches.
|
54
|
+
* Partial index searches
|
55
|
+
|
56
|
+
== Note on Patches/Pull Requests
|
57
|
+
|
58
|
+
* Fork the project.
|
59
|
+
* Make your feature addition or bug fix.
|
60
|
+
* Add tests for it. This is important so I don't break it in a
|
61
|
+
future version unintentionally.
|
62
|
+
* Commit, do not mess with rakefile, version, or history.
|
63
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
64
|
+
* Send me a pull request. Bonus points for topic branches.
|
65
|
+
|
66
|
+
== Copyright
|
67
|
+
|
68
|
+
Copyright (c) 2010 Adrian Pike. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
gem 'rspec', '>= 1.2.6'
|
6
|
+
gem 'rspec-rails', '>= 1.2.6'
|
7
|
+
require 'spec'
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
begin
|
11
|
+
require 'rspec/core/rake_task.rb'
|
12
|
+
require 'rspec/core/version'
|
13
|
+
rescue LoadError
|
14
|
+
puts "[formtastic:] RSpec - or one of it's dependencies - is not available. Install it with: sudo gem install rspec-rails"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'jeweler'
|
20
|
+
Jeweler::Tasks.new do |gem|
|
21
|
+
gem.name = "hyperion"
|
22
|
+
gem.summary = %Q{Hyperion is a sweet Ruby data model thats backed with Redis.}
|
23
|
+
gem.description = %Q{Hyperion is a sweet Ruby data model thats backed with Redis. It's designed to be screamin' fast.}
|
24
|
+
gem.email = "adrian.pike@gmail.com"
|
25
|
+
gem.homepage = "http://github.com/adrianpike/hyperion"
|
26
|
+
gem.authors = ["adrianpike"]
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Default: run unit specs.'
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
require 'rake/rdoctask'
|
36
|
+
Rake::RDocTask.new do |rdoc|
|
37
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
38
|
+
|
39
|
+
rdoc.rdoc_dir = 'rdoc'
|
40
|
+
rdoc.title = "hyperion #{version}"
|
41
|
+
rdoc.rdoc_files.include('README*')
|
42
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
if defined?(Spec)
|
47
|
+
desc 'Test hyperion.'
|
48
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
49
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
50
|
+
t.spec_opts = ["-c"]
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Test hyperion with specdoc formatting and colors'
|
54
|
+
Spec::Rake::SpecTask.new('specdoc') do |t|
|
55
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
56
|
+
t.spec_opts = ["--format specdoc", "-c"]
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Run all examples with RCov"
|
60
|
+
Spec::Rake::SpecTask.new('examples_with_rcov') do |t|
|
61
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
62
|
+
t.rcov = true
|
63
|
+
t.rcov_opts = ['--exclude', 'spec,Library']
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if defined?(RSpec)
|
68
|
+
desc 'Test hyperion.'
|
69
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
70
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
71
|
+
end
|
72
|
+
|
73
|
+
desc 'Test hyperion with specdoc formatting and colors'
|
74
|
+
RSpec::Core::RakeTask.new('specdoc') do |t|
|
75
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Run all examples with RCov"
|
79
|
+
RSpec::Core::RakeTask.new('examples_with_rcov') do |t|
|
80
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
81
|
+
t.rcov = true
|
82
|
+
t.rcov_opts = ['--exclude', 'spec,Library']
|
83
|
+
end
|
84
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/benchmarks.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'ruby-prof'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
5
|
+
require 'hyperion'
|
6
|
+
|
7
|
+
class DefaultKey < Hyperion
|
8
|
+
attr_accessor :content, :key
|
9
|
+
hyperion_key :key
|
10
|
+
end
|
11
|
+
|
12
|
+
class NoKey < Hyperion
|
13
|
+
attr_accessor :content
|
14
|
+
end
|
15
|
+
|
16
|
+
class IndexedObject < Hyperion
|
17
|
+
attr_accessor :content, :other_content, :key
|
18
|
+
|
19
|
+
hyperion_key :key
|
20
|
+
hyperion_index :other_content
|
21
|
+
hyperion_index [:content, :other_content]
|
22
|
+
end
|
23
|
+
|
24
|
+
[100,1000,10000,100000].each {|n|
|
25
|
+
puts "===== #{n} ====="
|
26
|
+
puts "#{n} inserts:"
|
27
|
+
bm = Benchmark.measure {
|
28
|
+
n.times do |k|
|
29
|
+
f = DefaultKey.new({
|
30
|
+
:key => k,
|
31
|
+
:content => "Benchmarking!"
|
32
|
+
})
|
33
|
+
f.save
|
34
|
+
end
|
35
|
+
}
|
36
|
+
p bm
|
37
|
+
|
38
|
+
puts "#{n} fetches:"
|
39
|
+
bm = Benchmark.measure {
|
40
|
+
n.times do |k|
|
41
|
+
f = DefaultKey.find(k)
|
42
|
+
end
|
43
|
+
}
|
44
|
+
p bm
|
45
|
+
|
46
|
+
puts "#{n} inserts w/autoincrement:"
|
47
|
+
bm = Benchmark.measure {
|
48
|
+
n.times do |k|
|
49
|
+
f = DefaultKey.new({
|
50
|
+
:content => "Benchmarking!"
|
51
|
+
})
|
52
|
+
f.save
|
53
|
+
end
|
54
|
+
}
|
55
|
+
p bm
|
56
|
+
|
57
|
+
puts "#{n} inserts with indexes and autoincrement:"
|
58
|
+
bm = Benchmark.measure {
|
59
|
+
n.times do |k|
|
60
|
+
f = IndexedObject.new({
|
61
|
+
:content => "Benchmarking!",
|
62
|
+
:other_content => k
|
63
|
+
})
|
64
|
+
f.save
|
65
|
+
end
|
66
|
+
}
|
67
|
+
p bm
|
68
|
+
|
69
|
+
puts "#{n} fetches on a single index:"
|
70
|
+
bm = Benchmark.measure {
|
71
|
+
n.times do |k|
|
72
|
+
f = IndexedObject.find({
|
73
|
+
:other_content => k
|
74
|
+
})
|
75
|
+
end
|
76
|
+
}
|
77
|
+
p bm
|
78
|
+
|
79
|
+
puts "#{n} fetches on a complex index:"
|
80
|
+
bm = Benchmark.measure {
|
81
|
+
n.times do |k|
|
82
|
+
f = IndexedObject.find({
|
83
|
+
:content => 'Benchmarking!',
|
84
|
+
:other_content => k
|
85
|
+
})
|
86
|
+
end
|
87
|
+
}
|
88
|
+
p bm
|
89
|
+
|
90
|
+
|
91
|
+
puts "#{n} indexless updates:"
|
92
|
+
bm = Benchmark.measure {
|
93
|
+
n.times do |k|
|
94
|
+
f = DefaultKey.find(k)
|
95
|
+
f.content = 'Second time around'
|
96
|
+
f.save
|
97
|
+
end
|
98
|
+
}
|
99
|
+
p bm
|
100
|
+
}
|
data/hyperion.gemspec
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{hyperion}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["adrianpike"]
|
12
|
+
s.date = %q{2010-10-28}
|
13
|
+
s.description = %q{Hyperion is a sweet Ruby data model thats backed with Redis. It's designed to be screamin' fast.}
|
14
|
+
s.email = %q{adrian.pike@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"benchmarks.rb",
|
27
|
+
"hyperion.gemspec",
|
28
|
+
"lib/hyperion.rb",
|
29
|
+
"lib/hyperion/base.rb",
|
30
|
+
"lib/hyperion/finders.rb",
|
31
|
+
"lib/hyperion/indices.rb",
|
32
|
+
"lib/hyperion/keys.rb",
|
33
|
+
"lib/hyperion/logger.rb",
|
34
|
+
"lib/hyperion/version.rb",
|
35
|
+
"spec/hyperion_spec.rb",
|
36
|
+
"spec/spec_helper.rb"
|
37
|
+
]
|
38
|
+
s.homepage = %q{http://github.com/adrianpike/hyperion}
|
39
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = %q{1.3.7}
|
42
|
+
s.summary = %q{Hyperion is a sweet Ruby data model thats backed with Redis.}
|
43
|
+
s.test_files = [
|
44
|
+
"spec/hyperion_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
else
|
54
|
+
end
|
55
|
+
else
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
data/lib/hyperion.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'yaml'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
# TODO: splat this
|
6
|
+
require 'hyperion/base'
|
7
|
+
require 'hyperion/indices'
|
8
|
+
require 'hyperion/keys'
|
9
|
+
require 'hyperion/logger'
|
10
|
+
require 'hyperion/version'
|
11
|
+
|
12
|
+
class Hyperion
|
13
|
+
extend Indices
|
14
|
+
extend Keys
|
15
|
+
extend Version
|
16
|
+
extend Logger
|
17
|
+
|
18
|
+
DEBUG = false
|
19
|
+
# TODO: ActiveModel lint it _mayhaps_
|
20
|
+
# TODO: atomic operations
|
21
|
+
# TODO: default key called #{class}_id if there isn't any @@redis_key
|
22
|
+
|
23
|
+
def initialize(opts = {})
|
24
|
+
defaults = (self.class.class_variable_defined?('@@redis_defaults') ? self.class.class_variable_get('@@redis_defaults') : {})
|
25
|
+
|
26
|
+
defaults.merge(opts).each {|k,v|
|
27
|
+
self.send(k.to_s+'=',v)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.first(conds)
|
32
|
+
# FIXME: gotta be a faster way ;)
|
33
|
+
self.find(conds).first
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find(conds)
|
37
|
+
Hyperion.logger.debug("[RS] Searching for #{conds.inspect}") if Hyperion::DEBUG
|
38
|
+
|
39
|
+
if conds.is_a? Hash then
|
40
|
+
Hyperion.logger.debug("[RS] Its a Hash, digging through indexes!") if Hyperion::DEBUG
|
41
|
+
ids = []
|
42
|
+
index_keys = []
|
43
|
+
index_values = []
|
44
|
+
|
45
|
+
if conds.keys.size > 1 then
|
46
|
+
conds.sort.each {|k,v|
|
47
|
+
index_values << v
|
48
|
+
index_keys << k.to_s
|
49
|
+
}
|
50
|
+
index_key = self.to_s.downcase + '_' + index_keys.join('.') + '_' + index_values.join('.')
|
51
|
+
ids << Hyperion.redis.smembers(index_key)
|
52
|
+
else
|
53
|
+
conds.each{|k,v|
|
54
|
+
index_key = self.to_s.downcase + '_' + k.to_s + '_' + v.to_s
|
55
|
+
ids << Hyperion.redis.smembers(index_key)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
ids.flatten.uniq.collect{|i|
|
59
|
+
self.find(i)
|
60
|
+
}
|
61
|
+
else
|
62
|
+
Hyperion.logger.debug("[RS] Fetching #{self.to_s.downcase + '_' + conds.to_s}") if Hyperion::DEBUG
|
63
|
+
v = redis[self.to_s.downcase + '_' + conds.to_s].to_s
|
64
|
+
if v and not v.empty? then
|
65
|
+
self.deserialize(v)
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.deserialize(what); YAML.load(what); end
|
73
|
+
def serialize; YAML::dump(self); end
|
74
|
+
|
75
|
+
def save
|
76
|
+
Hyperion.logger.debug("[RS] Saving a #{self.class.to_s}:") if Hyperion::DEBUG
|
77
|
+
|
78
|
+
unless (self.class.class_variable_defined?('@@redis_key')) then
|
79
|
+
self.class.send('attr_accessor', 'id')
|
80
|
+
self.class.class_variable_set('@@redis_key', 'id')
|
81
|
+
end
|
82
|
+
|
83
|
+
unless (self.send(self.class.class_variable_get('@@redis_key'))) then
|
84
|
+
Hyperion.logger.debug("[RS] Generating new key!") if Hyperion::DEBUG
|
85
|
+
self.send(self.class.class_variable_get('@@redis_key').to_s + '=', new_key)
|
86
|
+
end
|
87
|
+
|
88
|
+
Hyperion.logger.debug("[RS] Saving into #{full_key}: #{self.inspect}") if Hyperion::DEBUG
|
89
|
+
Hyperion.redis[full_key] = self.serialize
|
90
|
+
|
91
|
+
# Now lets update any indexes
|
92
|
+
# BUG: need to clear out any old indexes of us
|
93
|
+
self.class.class_variable_get('@@redis_indexes').each{|idx|
|
94
|
+
Hyperion.logger.debug("[RS] Updating index for #{idx}") if Hyperion::DEBUG
|
95
|
+
|
96
|
+
if idx.is_a?(Array) then
|
97
|
+
index_values = idx.sort.collect {|i| self.send(i) }.join('.')
|
98
|
+
index_key = self.class.to_s.downcase + '_' + idx.sort.join('.').to_s + '_' + index_values
|
99
|
+
else
|
100
|
+
value = self.send(idx)
|
101
|
+
index_key = self.class.to_s.downcase + '_' + idx.to_s + '_' + value.to_s if value
|
102
|
+
end
|
103
|
+
Hyperion.logger.debug("[RS] Saving index #{index_key}: #{self.send(self.class.class_variable_get('@@redis_key'))}") if Hyperion::DEBUG
|
104
|
+
Hyperion.redis.sadd(index_key, self.send(self.class.class_variable_get('@@redis_key')))
|
105
|
+
} if self.class.class_variable_defined?('@@redis_indexes')
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.dump(output = STDOUT, lock = false)
|
109
|
+
# TODO: lockability and progress
|
110
|
+
output.write(<<-eos)
|
111
|
+
# Hyperion Dump
|
112
|
+
# Generated by @adrianpike's Hyperion gem.
|
113
|
+
eos
|
114
|
+
output.write('# Generated on ' + Time.current.to_s + "\n")
|
115
|
+
output.write('# DB size is ' + redis.dbsize.to_s + "\n")
|
116
|
+
|
117
|
+
redis.keys.each{|k|
|
118
|
+
case redis.type(k)
|
119
|
+
when "string"
|
120
|
+
output.write({ k => redis.get(k)}.to_yaml)
|
121
|
+
when "set"
|
122
|
+
output.write({ k => redis.smembers(k) }.to_yaml)
|
123
|
+
end
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# THIS IS MAD DANGEROUS AND UNTESTED, BEWARE DATA INTEGRITY
|
129
|
+
def self.load(file = STDIN, truncate = true, lock = false)
|
130
|
+
# TODO: lockability and progress
|
131
|
+
|
132
|
+
YAML.each_document( file ) do |ydoc|
|
133
|
+
ydoc.each {|k,v|
|
134
|
+
redis.del(k) if truncate
|
135
|
+
|
136
|
+
case v.class.to_s
|
137
|
+
when 'String'
|
138
|
+
redis[k] = v
|
139
|
+
when 'Array'
|
140
|
+
v.each{|val|
|
141
|
+
redis.sadd(k,val)
|
142
|
+
}
|
143
|
+
else
|
144
|
+
p v.class
|
145
|
+
end
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
# THIS IS TOTALLY IRREVERSIBLE YO
|
152
|
+
def self.truncate!
|
153
|
+
redis.flushdb
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def new_key
|
158
|
+
if (self.class.class_variable_defined?('@@redis_generate_key') and self.class.class_variable_get('@@redis_generate_key') == false)
|
159
|
+
raise NoKey
|
160
|
+
else
|
161
|
+
Hyperion.redis.incr(self.class.to_s.downcase + '_' + self.class.class_variable_get('@@redis_key').to_s)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def full_key
|
166
|
+
self.class.to_s.downcase + '_' + self.send(self.class.class_variable_get('@@redis_key')).to_s
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Hyperion
|
2
|
+
|
3
|
+
class HyperionException < Exception; end
|
4
|
+
class NoKey < HyperionException; end
|
5
|
+
|
6
|
+
def self.hyperion_defaults(defaults)
|
7
|
+
class_variable_set(:@@redis_defaults, defaults)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.redis
|
11
|
+
@@redis ||= Redis.new
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hyperion
|
2
|
+
module Indices
|
3
|
+
|
4
|
+
class NoIndex < HyperionException; end
|
5
|
+
|
6
|
+
def hyperion_index(index)
|
7
|
+
if class_variable_defined?(:@@redis_indexes) then
|
8
|
+
class_variable_set(:@@redis_indexes, class_variable_get(:@@redis_indexes) << index)
|
9
|
+
else
|
10
|
+
class_variable_set(:@@redis_indexes, [index])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def reindex!
|
15
|
+
# TODO
|
16
|
+
end
|
17
|
+
|
18
|
+
# Indexes need to be sorted sets!
|
19
|
+
# ZSET scores can supposedly be large floats, should explore max ZSET score size.
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
# TODO: truncate the redis store and/or stub it beforehand
|
3
|
+
|
4
|
+
describe 'Hyperion' do
|
5
|
+
it "should work for basic set/fetch" do
|
6
|
+
string1 = random_string
|
7
|
+
|
8
|
+
f = DefaultKey.new
|
9
|
+
f.key = 'test'
|
10
|
+
f.content = string1
|
11
|
+
f.save
|
12
|
+
|
13
|
+
f2 = DefaultKey.find('test')
|
14
|
+
f2.content.should == f.content
|
15
|
+
|
16
|
+
f3 = IndexedObject.find(string1)
|
17
|
+
f3.should == nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to index a single attribute" do
|
21
|
+
string1 = random_string
|
22
|
+
|
23
|
+
f = IndexedObject.new
|
24
|
+
f.content = string1
|
25
|
+
f.other_content = 'zero_cool'
|
26
|
+
f.key = 'testery'
|
27
|
+
f.save
|
28
|
+
|
29
|
+
f2 = IndexedObject.first(:other_content => 'zero_cool')
|
30
|
+
f2.content.should == f.content
|
31
|
+
|
32
|
+
f3 = IndexedObject.find('testery')
|
33
|
+
f3.content.should == f.content
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should take objects at instantiation time" do
|
37
|
+
string1 = random_string
|
38
|
+
|
39
|
+
n=NoKey.new(:content => string1)
|
40
|
+
n.save
|
41
|
+
|
42
|
+
n2 = NoKey.find(n.id)
|
43
|
+
n2.content.should == string1
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should autogenerate keys" do
|
47
|
+
string1 = random_string
|
48
|
+
|
49
|
+
n=NoKey.new
|
50
|
+
n.content = string1
|
51
|
+
n.save
|
52
|
+
|
53
|
+
n2 = NoKey.find(n.id)
|
54
|
+
n2.content.should == string1
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should autoincrement keys whether specified or not' do
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to index multiple attributes" do
|
61
|
+
string1 = random_string
|
62
|
+
|
63
|
+
f = IndexedObject.new
|
64
|
+
f.content = string1
|
65
|
+
f.other_content = 'zero_cool'
|
66
|
+
f.key = 'testery'
|
67
|
+
f.save
|
68
|
+
|
69
|
+
f2 = IndexedObject.first(:other_content => 'zero_cool')
|
70
|
+
f2.content.should == f.content
|
71
|
+
|
72
|
+
f3 = IndexedObject.find('testery')
|
73
|
+
f3.content.should == f.content
|
74
|
+
|
75
|
+
f4 = IndexedObject.first(:other_content => 'zero_cool', :content => string1)
|
76
|
+
f4.content.should == f.content
|
77
|
+
end
|
78
|
+
|
79
|
+
it "shouldn't put an index in for an empty value" do
|
80
|
+
string1 = random_string
|
81
|
+
|
82
|
+
f = IndexedObject.new
|
83
|
+
f.content = string1
|
84
|
+
f.save
|
85
|
+
|
86
|
+
f2 = IndexedObject.first(:other_content => nil)
|
87
|
+
f2.should == nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it "shouldn't matter what order your indexes are specified" do
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not have concurrency issues" do
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be able to reindex objects" do
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should update indexes" do
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should be OK with key collision' do
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should die if there's no redis server around" do
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should allow crazy lengths and contents for both keys and values" do
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
require 'hyperion'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.mock_with :rspec
|
9
|
+
end
|
10
|
+
|
11
|
+
class DefaultKey < Hyperion
|
12
|
+
attr_accessor :content, :key
|
13
|
+
hyperion_key :key
|
14
|
+
end
|
15
|
+
|
16
|
+
class NoKey < Hyperion
|
17
|
+
attr_accessor :content
|
18
|
+
end
|
19
|
+
|
20
|
+
class IndexedObject < Hyperion
|
21
|
+
attr_accessor :content, :other_content, :key
|
22
|
+
|
23
|
+
hyperion_key :key
|
24
|
+
hyperion_index :other_content
|
25
|
+
hyperion_index [:content, :other_content]
|
26
|
+
end
|
27
|
+
|
28
|
+
def random_string(length = 10)
|
29
|
+
'asdfdsfasd' # todo lolololol
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hyperion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- adrianpike
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-28 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Hyperion is a sweet Ruby data model thats backed with Redis. It's designed to be screamin' fast.
|
22
|
+
email: adrian.pike@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- .document
|
32
|
+
- .gitignore
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- benchmarks.rb
|
38
|
+
- hyperion.gemspec
|
39
|
+
- lib/hyperion.rb
|
40
|
+
- lib/hyperion/base.rb
|
41
|
+
- lib/hyperion/finders.rb
|
42
|
+
- lib/hyperion/indices.rb
|
43
|
+
- lib/hyperion/keys.rb
|
44
|
+
- lib/hyperion/logger.rb
|
45
|
+
- lib/hyperion/version.rb
|
46
|
+
- spec/hyperion_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/adrianpike/hyperion
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options:
|
54
|
+
- --charset=UTF-8
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.7
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Hyperion is a sweet Ruby data model thats backed with Redis.
|
80
|
+
test_files:
|
81
|
+
- spec/hyperion_spec.rb
|
82
|
+
- spec/spec_helper.rb
|