hyperion 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|