memodis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ test/config
23
+ test/log
24
+ test/pid
25
+ test/dump.rdb
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "vendor/levicook/redis-rb"]
2
+ path = vendor/levicook/redis-rb
3
+ url = git@github.com:levicook/redis-rb.git
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 levicook@gmail.com
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,17 @@
1
+ = memodis
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (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)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 levicook@gmail.com. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,146 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "memodis"
8
+ gem.summary = %Q{redis backed memoization helpers}
9
+ gem.description = %Q{
10
+ semi-transparent memoization; backed by redis; redis-rb and redis-namespace
11
+
12
+ Background
13
+ -----------
14
+ 1) http://blog.grayproductions.net/articles/caching_and_memoization
15
+ 2) http://code.google.com/p/redis & http://github.com/ezmobius/redis-rb
16
+
17
+ Important Moving Parts
18
+ ----------------------
19
+ 1) http://code.google.com/p/redis/wiki/GetCommand
20
+ 2) http://code.google.com/p/redis/wiki/SetCommand
21
+ 3) http://code.google.com/p/redis/wiki/SetnxCommand
22
+ 4) http://github.com/defunkt/redis-namespace
23
+ }
24
+ gem.email = "levicook@gmail.com"
25
+ gem.homepage = "http://github.com/levicook/memodis"
26
+ gem.authors = ["levicook@gmail.com"]
27
+ gem.add_development_dependency "riot", ">= 0"
28
+ gem.add_development_dependency "reek", ">= 0"
29
+ gem.add_development_dependency "daemon_controller", ">= 0"
30
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
31
+ end
32
+ Jeweler::GemcutterTasks.new
33
+ rescue LoadError
34
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
35
+ end
36
+
37
+ require 'rake/testtask'
38
+ Rake::TestTask.new(:test) do |test|
39
+ test.libs << 'lib' << 'test'
40
+ test.pattern = 'test/**/*_test.rb'
41
+ test.verbose = true
42
+ end
43
+
44
+ begin
45
+ require 'rcov/rcovtask'
46
+ Rcov::RcovTask.new do |test|
47
+ test.libs << 'test'
48
+ test.pattern = 'test/**/*_test.rb'
49
+ test.verbose = true
50
+ end
51
+ rescue LoadError
52
+ task :rcov do
53
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
54
+ end
55
+ end
56
+
57
+ task :test => :check_dependencies
58
+
59
+ begin
60
+ require 'reek/adapters/rake_task'
61
+ Reek::RakeTask.new do |t|
62
+ t.fail_on_error = true
63
+ t.verbose = false
64
+ t.source_files = 'lib/**/*.rb'
65
+ end
66
+ rescue LoadError
67
+ task :reek do
68
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
69
+ end
70
+ end
71
+
72
+ begin
73
+ require 'roodi'
74
+ require 'roodi_task'
75
+ RoodiTask.new do |t|
76
+ t.verbose = false
77
+ end
78
+ rescue LoadError
79
+ task :roodi do
80
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
81
+ end
82
+ end
83
+
84
+ task :default => :test
85
+
86
+ require 'rake/rdoctask'
87
+ Rake::RDocTask.new do |rdoc|
88
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
89
+
90
+ rdoc.rdoc_dir = 'rdoc'
91
+ rdoc.title = "memodis #{version}"
92
+ rdoc.rdoc_files.include('README*')
93
+ rdoc.rdoc_files.include('lib/**/*.rb')
94
+ end
95
+
96
+
97
+
98
+ # redis test environment...
99
+
100
+ def write_redis_config(confile, port, slaveof=nil)
101
+ pidfile = File.expand_path("test/pid/#{ File.basename(confile, '.*') }.pid")
102
+ logfile = File.expand_path("test/log/#{ File.basename(confile, '.*') }.log")
103
+ open(confile, 'w') do |f|
104
+ f.puts "daemonize yes"
105
+ f.puts "port #{port}"
106
+ f.puts "pidfile #{pidfile}"
107
+ f.puts "bind 127.0.0.1"
108
+ f.puts "timeout 0"
109
+ f.puts "dir ./test"
110
+ f.puts "loglevel notice"
111
+ f.puts "logfile #{logfile}"
112
+ f.puts slaveof unless slaveof.nil?
113
+ f.puts "databases 2"
114
+ f.puts "maxmemory 6291456"
115
+ f.puts "glueoutputbuf yes"
116
+ f.puts "shareobjects no"
117
+ f.puts "shareobjectspoolsize 1024"
118
+ end
119
+ end
120
+
121
+ directory 'test/config'
122
+ directory 'test/log'
123
+ directory 'test/pid'
124
+
125
+ (16379..16380).each do |port|
126
+ config_file = "test/config/redis-server:#{port}.conf"
127
+ file config_file => %w(test/config test/log test/pid) do
128
+ write_redis_config(config_file, port)
129
+ end
130
+ namespace :test do
131
+ task :config => config_file
132
+ end
133
+ end
134
+
135
+ (16389..16394).each do |port|
136
+ config_file = "test/config/redis-server:#{port}.conf"
137
+ file config_file => %w(test/config test/log test/pid) do
138
+ master_port = port.even? ? 16379 : 16380
139
+ write_redis_config(config_file, port, "slaveof 127.0.0.1 #{master_port}")
140
+ end
141
+ namespace :test do
142
+ task :config => config_file
143
+ end
144
+ end
145
+
146
+ task :test => 'test:config'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/examples/main.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'pathname'
2
+ load Pathname.new(__FILE__).parent.parent + 'lib/memodis.rb'
3
+
4
+ require 'rubygems'
5
+ require 'hitimes'
6
+
7
+ def fib(num)
8
+ return num if num < 2
9
+ fib(num - 1) + fib(num - 2)
10
+ end
11
+
12
+ puts 'Before memoize: '
13
+ puts Hitimes::Interval.measure { print fib(33); print ': ' }
14
+
15
+
16
+ extend Memodis
17
+
18
+ memoize :fib, Memodis::DistCache.new({
19
+ :key_gen => lambda { |k| "fib(#{k})" },
20
+ :decoder => :integer,
21
+ :expires => (10 * 60),
22
+ :master => '127.0.0.1:16379 127.0.0.1:16380'.split,
23
+ :slaves => '127.0.0.1:16389 127.0.0.1:16390
24
+ 127.0.0.1:16391 127.0.0.1:16392
25
+ 127.0.0.1:16393 127.0.0.1:16394'.split
26
+ })
27
+
28
+ puts "\nAfter memoize: "
29
+ puts Hitimes::Interval.measure { print fib(33); print ': ' }
30
+ puts Hitimes::Interval.measure { print fib(33); print ': ' }
31
+ puts Hitimes::Interval.measure { print fib(33); print ': ' }
@@ -0,0 +1,10 @@
1
+
2
+ require "pathname"
3
+ vendor_path = Pathname.new(__FILE__).parent.parent + "vendor"
4
+ Pathname.glob("#{vendor_path}/**/lib") do |lib|
5
+ next if $LOAD_PATH.include?(lib)
6
+ $LOAD_PATH.insert(0, lib.realpath.to_s) if lib.directory?
7
+ end
8
+
9
+ require "dist_redis"
10
+
data/lib/memodis.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'pathname'
2
+ load Pathname.new(__FILE__).parent+'dependencies.rb'
3
+
4
+ require 'memodis/dist_cache'
5
+
6
+ module Memodis
7
+
8
+ # slurp cool vendor goodies into our namespace. would declare them
9
+ # as gem dependencies, but they don't seem to be published...
10
+
11
+ VENDOR_PATH = Pathname.new(__FILE__).parent.parent + 'vendor'
12
+
13
+ class_eval((VENDOR_PATH+'memoizable.rb').read) unless defined? Memodis::Memoizable
14
+ class_eval((VENDOR_PATH+'weak_cache.rb').read) unless defined? Memodis::WeakCache
15
+
16
+ include Memodis::Memoizable
17
+
18
+ end
@@ -0,0 +1,88 @@
1
+ module Memodis
2
+
3
+ class DistCache
4
+
5
+ CODERS = {}
6
+ CODERS.default = lambda { |v| v }
7
+ CODERS[:float] = lambda { |v| Float(v) }
8
+ CODERS[:integer] = lambda { |v| Integer(v) }
9
+ CODERS.freeze
10
+
11
+ def initialize(options)
12
+
13
+ @master = DistRedis.new({
14
+ :db => options[:db],
15
+ :hosts => options[:master],
16
+ :timeout => options[:timeout],
17
+ })
18
+
19
+ @slaves = options[:slaves].map do |h|
20
+ host, port = h.split(':')
21
+ Redis.new({
22
+ :db => options[:db],
23
+ :host => host,
24
+ :port => port,
25
+ :timeout => options[:timeout],
26
+ })
27
+ end
28
+
29
+ @encoder = case options[:encoder]
30
+ when Proc; options[:encoder]
31
+ else; CODERS[options[:encoder]]
32
+ end
33
+
34
+ @decoder = case options[:decoder]
35
+ when Proc; options[:decoder]
36
+ else; CODERS[options[:decoder]]
37
+ end
38
+
39
+ @key_gen = options.fetch(:key_gen, lambda { |k| k })
40
+
41
+ @expires = options[:expires]
42
+ end
43
+
44
+ def []= key, val
45
+ key = @key_gen.call(key)
46
+ @master.set(key, encode(val))
47
+ @master.expire(key, @expires) unless @expires.nil?
48
+ end
49
+
50
+ def [] key
51
+ key = @key_gen.call(key)
52
+ if val = get(key)
53
+ decode(val)
54
+ else
55
+ nil # don't decode a miss
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def indexed_slaves
62
+ @indexed_slaves ||= @slaves.inject(Hash.new) do |index, slave|
63
+ slave_info = slave.info
64
+ master_host = slave_info[:master_host]
65
+ master_port = slave_info[:master_port]
66
+ index["#{master_host}:#{master_port}"] = slave
67
+ index
68
+ end
69
+ end
70
+
71
+ def get key
72
+ m_node = @master.node_for_key(String(key.first))
73
+ s_node = indexed_slaves[m_node.server] || m_node
74
+ # TODO log warning if s_node == m_node
75
+ s_node.get(key)
76
+ end
77
+
78
+ def decode(val)
79
+ @decoder.call(val)
80
+ end
81
+
82
+ def encode(val)
83
+ @encoder.call(val)
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,60 @@
1
+ require 'daemon_controller'
2
+ require 'socket'
3
+
4
+ module Memodis
5
+ class LocalRedisController
6
+
7
+ def self.start(config_file)
8
+ new(config_file).start
9
+ end
10
+
11
+ attr_reader :config, :controller
12
+
13
+ def initialize(config_file)
14
+ config = IO.readlines(config_file)
15
+ config.reject! { |l| l !~ /^(bind|port|pidfile|logfile)[ \t]?/ }
16
+ config.collect! { |l| l.chomp.split(/[ \t]/) }
17
+ config.flatten!
18
+ config = Hash[*config]
19
+ config['file'] = config_file
20
+ @config = config
21
+ @config.freeze
22
+
23
+ @controller = DaemonController.new({
24
+ :identifier => identifier,
25
+ :start_command => start_command,
26
+ :ping_command => ping_command,
27
+ :pid_file => pid_file,
28
+ :log_file => log_file
29
+ })
30
+ end
31
+
32
+ def connect
33
+ @controller.connect { TCPSocket.new(@config['bind'], @config['port']) }
34
+ end
35
+ alias :start :connect
36
+
37
+ private
38
+
39
+ def identifier
40
+ "redis-server #{@config['file']}"
41
+ end
42
+
43
+ def start_command
44
+ "redis-server #{@config['file']}"
45
+ end
46
+
47
+ def ping_command
48
+ lambda { TCPSocket.new(@config['bind'], @config['port']) }
49
+ end
50
+
51
+ def pid_file
52
+ @config['pidfile']
53
+ end
54
+
55
+ def log_file
56
+ @config['logfile']
57
+ end
58
+
59
+ end
60
+ end
data/script/mooch ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require "pathname"
3
+
4
+
5
+ LOADER = '''
6
+ require "pathname"
7
+
8
+ vendor_path = Pathname.new(__FILE__).parent.parent + 'vendor'
9
+
10
+ Pathname.glob("#{vendor_path}/**/lib") do |lib|
11
+ next if $LOAD_PATH.include?(lib)
12
+ $LOAD_PATH.insert(0, lib.realpath.to_s) if lib.directory?
13
+ end
14
+ '''
15
+
16
+
17
+ if ARGV[0] == "init"
18
+ lib = Pathname.new(ARGV[1])
19
+ lib.mkpath
20
+ (lib + 'dependencies.rb').open("w") do |file|
21
+ file.write LOADER
22
+ end
23
+ else
24
+ vendor = Pathname.new("vendor")
25
+ vendor.mkpath
26
+
27
+ if File.exist?('.git')
28
+ system("git add vendor")
29
+ system("git submodule add git://github.com/#{ARGV[0]}.git vendor/#{ARGV[0]}")
30
+ else
31
+ Dir.chdir(vendor.realpath)
32
+ system("git clone git://github.com/#{ARGV[0]}.git #{ARGV[0]}")
33
+ if ARGV[1]
34
+ Dir.chdir(ARGV[0])
35
+ system("git checkout #{ARGV[1]}")
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env watchr
2
+
3
+ watch( 'test/teststrap.rb' ) { |md| system("ruby -Ilib -Itest test/*_test.rb") }
4
+
5
+ watch( 'test/.*_test.rb' ) { |md| system("ruby -Ilib -Itest #{md[0]}") }
6
+
7
+ watch( 'lib/(.*)\.rb' ) { |md| system("ruby -Ilib -Itest test/#{md[1]}_test.rb") }
@@ -0,0 +1,12 @@
1
+ require 'teststrap'
2
+
3
+ context Memodis do
4
+ setup { Memodis }
5
+ asserts(:constants).includes('Memoizable')
6
+ asserts(:constants).includes('WeakCache')
7
+ end
8
+
9
+ context "Exteding Memodis" do
10
+ setup { Object.new.extend(Memodis) }
11
+ asserts(:methods).includes('memoize')
12
+ end
data/test/teststrap.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'riot'
3
+ require 'memodis'
4
+ Riot.dots
5
+
6
+
7
+ require 'memodis/local_redis_controller'
8
+ Dir['test/config/redis-server*.conf'].each do |config_file|
9
+ Memodis::LocalRedisController.start(config_file)
10
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # memoizable.rb
4
+ #
5
+ # Created by James Edward Gray II on 2006-01-21.
6
+ # Copyright 2006 Gray Productions. All rights reserved.
7
+
8
+ #
9
+ # Have your class or module <tt>extend Memoizable</tt> to gain access to the
10
+ # #memoize method.
11
+ #
12
+ module Memoizable
13
+ #
14
+ # This method is used to replace a computationally expensive method with an
15
+ # equivalent method that will answer repeat calls for indentical arguments
16
+ # from a _cache_. To use, make sure the current class extends Memoizable,
17
+ # then call by passing the _name_ of the method you wish to cache results for.
18
+ #
19
+ # The _cache_ object can be any object supporting both #[] and #[]=. The keys
20
+ # used for the _cache_ are an Array of the arguments the method was called
21
+ # with and the values are just the returned results of the original method
22
+ # call. The default _cache_ is a simple Hash, providing in-memory storage.
23
+ #
24
+ def memoize( name, cache = Hash.new )
25
+ original = "__unmemoized_#{name}__"
26
+
27
+ #
28
+ # <tt>self.class</tt> is used for the top level, to modify Object, otherwise
29
+ # we just modify the Class or Module directly
30
+ #
31
+ ([Class, Module].include?(self.class) ? self : self.class).class_eval do
32
+ alias_method original, name
33
+ private original
34
+ define_method(name) { |*args| cache[args] ||= send(original, *args) }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # This cache uses weak references, which can be garbage collected. When the
3
+ # cache is checked, the value will be returned if it is still around, otherwise
4
+ # +nil+ is returned.
5
+ #
6
+ # (Code by Mauricio Fernandez.)
7
+ #
8
+ class WeakCache
9
+ def initialize
10
+ set_internal_hash
11
+ end
12
+
13
+ def method_missing( meth, *args, &block )
14
+ __get_hash__.send(meth, *args, &block)
15
+ end
16
+
17
+ private
18
+
19
+ def __get_hash__
20
+ old_critical = Thread.critical
21
+ Thread.critical = true
22
+
23
+ @valid or set_internal_hash
24
+ return ObjectSpace._id2ref(@hash_id)
25
+ ensure
26
+ Thread.critical = old_critical
27
+ end
28
+
29
+ def set_internal_hash
30
+ hash = Hash.new
31
+ @hash_id = hash.object_id
32
+ @valid = true
33
+
34
+ ObjectSpace.define_finalizer(hash, lambda { @valid = false })
35
+ hash = nil
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memodis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - levicook@gmail.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-14 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: riot
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: reek
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: daemon_controller
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: |
46
+
47
+ semi-transparent memoization; backed by redis; redis-rb and redis-namespace
48
+
49
+ Background
50
+ -----------
51
+ 1) http://blog.grayproductions.net/articles/caching_and_memoization
52
+ 2) http://code.google.com/p/redis & http://github.com/ezmobius/redis-rb
53
+
54
+ Important Moving Parts
55
+ ----------------------
56
+ 1) http://code.google.com/p/redis/wiki/GetCommand
57
+ 2) http://code.google.com/p/redis/wiki/SetCommand
58
+ 3) http://code.google.com/p/redis/wiki/SetnxCommand
59
+ 4) http://github.com/defunkt/redis-namespace
60
+
61
+ email: levicook@gmail.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - LICENSE
68
+ - README.rdoc
69
+ files:
70
+ - .document
71
+ - .gitignore
72
+ - .gitmodules
73
+ - LICENSE
74
+ - README.rdoc
75
+ - Rakefile
76
+ - VERSION
77
+ - examples/main.rb
78
+ - lib/dependencies.rb
79
+ - lib/memodis.rb
80
+ - lib/memodis/dist_cache.rb
81
+ - lib/memodis/local_redis_controller.rb
82
+ - script/mooch
83
+ - script/test.watchr
84
+ - test/memodis_test.rb
85
+ - test/teststrap.rb
86
+ - vendor/memoizable.rb
87
+ - vendor/weak_cache.rb
88
+ has_rdoc: true
89
+ homepage: http://github.com/levicook/memodis
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options:
94
+ - --charset=UTF-8
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ version:
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.3.5
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: redis backed memoization helpers
116
+ test_files:
117
+ - test/memodis_test.rb
118
+ - test/teststrap.rb
119
+ - examples/main.rb