nofxx-tokyo_store 0.1.7

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,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.tch
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcos Piccinini
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,39 @@
1
+ = TokyoStore
2
+
3
+ Rack based Tokyo stored (FAST!) Rails session store.
4
+
5
+ Code:: http://github.com/nofxx/tokyo_store
6
+ Demo:: http://github.com/nofxx/tokyo_webapps under rails/session
7
+
8
+
9
+ == Require
10
+
11
+ Tokyo Cabinet/Tyrant:: http://tokyocabinet.sourceforge.net
12
+ Rufus Tokyo:: http://github.com/jmettraux/rufus-tokyo
13
+
14
+
15
+ == Install
16
+
17
+ gem install nofxx-tokyo_store
18
+
19
+ config.gem 'nofxx-tokyo_store', :lib => 'tokyo_store'
20
+ ActionController::Base.session_store = Rack::Session::Tokyo
21
+
22
+
23
+ == Run
24
+
25
+ Just start tyrant
26
+
27
+ ttserver data.tch
28
+
29
+
30
+ == Thanks
31
+
32
+ Mikio Hirabayashi:: tokyo products
33
+ John Mettraux:: rufus-tokyo
34
+ Luca Guidi:: redis-store - Rack code stolen from here =D
35
+
36
+
37
+ == Copyright
38
+
39
+ Copyright (c) 2009 Marcos Piccinini. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "tokyo_store"
8
+ gem.summary = "Tokyo Tyrant rails session store"
9
+ gem.email = "x@nofxx.com"
10
+ gem.homepage = "http://github.com/nofxx/tokyo_store"
11
+ gem.authors = ["Marcos Piccinini"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.pattern = 'spec/**/*_spec.rb'
23
+ spec.verbose = true
24
+ end
25
+
26
+ task :default => :spec
27
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.7
@@ -0,0 +1,108 @@
1
+ #
2
+ # Tokyo Store
3
+ #
4
+ # Benchmark vs MemCacheStore on memcached and tyrant
5
+ #
6
+ require 'rubygems'
7
+ require 'active_support'
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+ require 'tokyo_store'
11
+ #include TokyoCabinet
12
+
13
+ P = "x" * 100
14
+ M = P * 10
15
+ G = M * 10
16
+ OBJ = { :small => P, :medium => M, :big => G}
17
+
18
+ #TODO: Cabinet & memcached C bindings
19
+ @tokyo = ActiveSupport::Cache.lookup_store :tokyo_store, "localhost:1978"
20
+ @memca = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost:11211"
21
+ @memto = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost:1978"
22
+
23
+ # # # Read & Write
24
+ Benchmark.bmbm do |b|
25
+ b.report("TokyoStore# P") { 10_000.times { |i| @tokyo.write i.to_s, P }}
26
+ b.report("MemCacheD # P") { 10_000.times { |i| @memca.write i.to_s, P }}
27
+ b.report("MemCacheT # P") { 10_000.times { |i| @memto.write i.to_s, P }}
28
+ b.report("TokyoStore# M") { 10_000.times { |i| @tokyo.write i.to_s, M }}
29
+ b.report("MemCacheD # M") { 10_000.times { |i| @memca.write i.to_s, M }}
30
+ b.report("MemCacheT # M") { 10_000.times { |i| @memto.write i.to_s, M }}
31
+ b.report("TokyoStore# G") { 10_000.times { |i| @tokyo.write i.to_s, G }}
32
+ b.report("MemCacheD # G") { 10_000.times { |i| @memca.write i.to_s, G }}
33
+ b.report("MemCacheT # G") { 10_000.times { |i| @memto.write i.to_s, G }}
34
+ b.report("TokyoStore# OB") { 10_000.times { |i| @tokyo.write i.to_s, OBJ }}
35
+ b.report("MemCacheD # OB") { 10_000.times { |i| @memca.write i.to_s, OBJ }}
36
+ b.report("MemCacheT # OB") { 10_000.times { |i| @memto.write i.to_s, OBJ }}
37
+ b.report("TokyoStore# R") { 10_000.times { |i| @tokyo.read i.to_s }}
38
+ b.report("MemCacheD # R") { 10_000.times { |i| @memca.read i.to_s }}
39
+ b.report("MemCacheT # R") { 10_000.times { |i| @memto.read i.to_s }}
40
+ b.report("TokyoStore# E") { 10_000.times { |i| @tokyo.exist? i.to_s }}
41
+ b.report("MemCacheD # E") { 10_000.times { |i| @memca.exist? i.to_s }}
42
+ b.report("MemCacheT # E") { 10_000.times { |i| @memto.exist? i.to_s }}
43
+ b.report("TokyoStore# D") { 10_000.times { |i| @tokyo.delete i.to_s }}
44
+ b.report("MemCacheD # D") { 10_000.times { |i| @memca.delete i.to_s }}
45
+ b.report("MemCacheT # D") { 10_000.times { |i| @memto.delete i.to_s }}
46
+ b.report("TokyoStore# +") { 10_000.times { |i| @tokyo.increment i.to_s }}
47
+ b.report("MemCacheD # +") { 10_000.times { |i| @memca.increment i.to_s }}
48
+ b.report("MemCacheT # +") { 10_000.times { |i| @memto.increment i.to_s }}
49
+ b.report("TokyoStore# -") { 10_000.times { |i| @tokyo.decrement i.to_s }}
50
+ b.report("MemCacheD # -") { 10_000.times { |i| @memca.decrement i.to_s }}
51
+ b.report("MemCacheT # -") { 10_000.times { |i| @memto.decrement i.to_s }}
52
+ end
53
+
54
+ puts
55
+ thr = []
56
+ # # Read & Write
57
+ Benchmark.bmbm do |b|
58
+ b.report("Tokyo # W") { 100.times { |j| thr << Thread.new { 100.times { |i| @tokyo.write "#{j}-#{i}", OBJ }}}; thr.each { |t| t.join }; thr = [] }
59
+ b.report("MemCa # W") { 100.times { |j| thr << Thread.new { 100.times { |i| @memca.write "#{j}-#{i}", OBJ}}}; thr.each { |t| t.join }; thr = [] }
60
+ b.report("MemTo # W") { 100.times { |j| thr << Thread.new { 100.times { |i| @memto.write "#{j}-#{i}", OBJ}}}; thr.each { |t| t.join }; thr = [] }
61
+ b.report("Tokyo # R") { 100.times { |j| thr << Thread.new { 100.times { |i| @tokyo.read "#{j}-#{i}" }}}; thr.each { |t| t.join }; thr = [] }
62
+ b.report("MemCa # R") { 100.times { |j| thr << Thread.new { 100.times { |i| @memca.read "#{j}-#{i}"}}}; thr.each { |t| t.join }; thr = [] }
63
+ b.report("MemTo # R") { 100.times { |j| thr << Thread.new { 100.times { |i| @memto.read "#{j}-#{i}"}}}; thr.each { |t| t.join }; thr = [] }
64
+ end
65
+
66
+ __END__
67
+
68
+
69
+
70
+ Core 2 Duo 8500 - 3.16Ghz
71
+ ------------------------------------------------------------
72
+ TokyoStore# P 0.140000 0.090000 0.230000 ( 0.367587)
73
+ MemCacheD # P 0.570000 0.160000 0.730000 ( 0.829167)
74
+ MemCacheT # P 0.560000 0.170000 0.730000 ( 0.897084)
75
+ TokyoStore# M 0.170000 0.100000 0.270000 ( 0.448071)
76
+ MemCacheD # M 0.610000 0.140000 0.750000 ( 0.878559)
77
+ MemCacheT # M 0.630000 0.140000 0.770000 ( 0.951748)
78
+ TokyoStore# G 0.410000 0.090000 0.500000 ( 0.976746)
79
+ MemCacheD # G 1.000000 0.200000 1.200000 ( 1.429635)
80
+ MemCacheT # G 1.060000 0.170000 1.230000 ( 1.558731)
81
+ TokyoStore# OB 0.480000 0.130000 0.610000 ( 1.299556)
82
+ MemCacheD # OB 1.460000 0.280000 1.740000 ( 1.980678)
83
+ MemCacheT # OB 1.230000 0.230000 1.460000 ( 1.856561)
84
+ TokyoStore# R 0.620000 0.230000 0.850000 ( 1.204192)
85
+ MemCacheD # R 0.550000 0.110000 0.660000 ( 0.748446)
86
+ MemCacheT # R 1.490000 0.260000 1.750000 ( 2.142504)
87
+ TokyoStore# P 0.150000 0.090000 0.240000 ( 0.393119)
88
+ MemCacheD # P 0.640000 0.150000 0.790000 ( 0.896260)
89
+ MemCacheT # P 0.660000 0.170000 0.830000 ( 0.966585)
90
+ TokyoStore# E 0.120000 0.100000 0.220000 ( 0.359803)
91
+ MemCacheD # E 0.720000 0.130000 0.850000 ( 0.969214)
92
+ MemCacheT # E 0.820000 0.160000 0.980000 ( 1.195356)
93
+ TokyoStore# D 0.220000 0.170000 0.390000 ( 0.707877)
94
+ MemCacheD # D 0.560000 0.110000 0.670000 ( 0.775865)
95
+ MemCacheT # D 0.560000 0.140000 0.700000 ( 0.860964)
96
+ TokyoStore# + 0.230000 0.190000 0.420000 ( 0.654690)
97
+ MemCacheD # + 0.570000 0.130000 0.700000 ( 0.801407)
98
+ MemCacheT # + 0.540000 0.190000 0.730000 ( 0.937882)
99
+ TokyoStore# - 0.200000 0.210000 0.410000 ( 0.669899)
100
+ MemCacheD # - 0.600000 0.100000 0.700000 ( 0.778212)
101
+ MemCacheT # - 0.600000 0.140000 0.740000 ( 0.983345)
102
+
103
+ Tokyo # W 0.500000 0.100000 0.600000 ( 1.189529)
104
+ MemCa # W 2.390000 0.250000 2.640000 ( 2.942472)
105
+ MemTo # W 2.550000 0.260000 2.810000 ( 3.121612)
106
+ Tokyo # R 0.660000 0.230000 0.890000 ( 1.266911)
107
+ MemCa # R 1.550000 0.190000 1.740000 ( 1.824773)
108
+ MemTo # R 3.170000 0.280000 3.450000 ( 3.908895)
@@ -0,0 +1,138 @@
1
+ require 'rufus/tokyo/tyrant'
2
+ # require 'tokyocabinet'
3
+
4
+ module ActiveSupport
5
+ module Cache
6
+
7
+ # A cache store implementation which stores data in Tokyo Cabinet
8
+ #
9
+ # Special features:
10
+ # - Clustering and load balancing. TODO
11
+ # - Time-based expiry support. TODO (Lua)
12
+ # - Per-request in memory cache for all communication with the Tokyo server(s).
13
+ class TokyoStore < Store
14
+
15
+ def self.build_tokyo(*store)
16
+ store = store.flatten
17
+ options = store.extract_options!
18
+ #TODO: multiple instances
19
+ store = store.empty? ? ["localhost", 1978] : store[0].split(":")
20
+
21
+ #TODO: Auto choice between tyrant ffi x tyrant pure ruby x cabinet C
22
+ # Tyrant FFI
23
+ Rufus::Tokyo::Tyrant.new(store[0], store[1].to_i)
24
+
25
+ # Cabinet C
26
+ #hdb = HDB.new
27
+ # if !hdb.open(store[0], HDB::OWRITER | HDB::OCREAT)
28
+ # ecode = hdb.ecode
29
+ # STDERR.printf("open error: %s\n", hdb.errmsg(ecode))
30
+ # end
31
+ # hdb
32
+ end
33
+
34
+ # Creates a new TokyoStore object, with the given tyrant server
35
+ # addresses. Each address is either a host name, or a host-with-port string
36
+ # in the form of "host_name:port". For example:
37
+ #
38
+ # ActiveSupport::Cache::TokyoStore.new("localhost", "server-downstairs.localnetwork:8229")
39
+ #
40
+ # If no addresses are specified, then TokyoStore will connect to
41
+ # localhost port 1978 (the default tyrant port).
42
+ def initialize(*store)
43
+ if store.first.respond_to?(:get)
44
+ @data = store.first
45
+ else
46
+ @data = self.class.build_tokyo(*store)
47
+ end
48
+
49
+ extend Strategy::LocalCache
50
+ end
51
+
52
+ # Reads multiple keys from the cache.
53
+ def read_multi(*keys)
54
+ #keys.inject({ }){ |h,k| h.merge({ k => read(k)}) }
55
+ @data.lget(keys).inject({ }) { |h, k| h.merge({ k[0] => Marshal.load(k[1])})} #
56
+ end
57
+
58
+ def read(key, options = nil) # :nodoc:
59
+ # TODO: benchmark [key] vs .get(key)
60
+ super
61
+ return nil unless val = @data[key]
62
+ val = Marshal.load(val) unless raw?(options)
63
+ val
64
+ # if str = @data.get(key)
65
+ # Marshal.load str
66
+ # else
67
+ # STDERR.printf("get error: %s\n", @data.errmsg(@data.ecode))
68
+ # end
69
+ # logger.error("TokyoError (#{e}): #{e.message}")
70
+ # nil
71
+ end
72
+
73
+ # Writes a value to the cache.
74
+ #
75
+ # Possible options:
76
+ # - +:unless_exist+ - set to true if you don't want to update the cache
77
+ # if the key is already set.
78
+ def write(key, value, options = nil)
79
+ super
80
+ method = options && options[:unless_exist] ? :add : :set
81
+ # will break the connection if you send it an integer
82
+ # in raw mode, so we convert it to a string to be sure it continues working.
83
+ value = raw?(options) ? value.to_s : Marshal.dump(value) # if value.instance_of? Hash
84
+
85
+ @data[key] = value
86
+ ###response = @data.put(key, value) || STDERR.printf("get error: %s\n", @data.errmsg(@data.ecode))#, expires_in(options), raw?(options))
87
+ # logger.error("TokyoError (#{e}): #{e.message}")
88
+ # false
89
+ end
90
+
91
+ def delete(key, options = nil) # :nodoc:
92
+ super
93
+ @data.delete(key) #= nil #, expires_in(options))
94
+ end
95
+
96
+ def exist?(key, options = nil) # :nodoc:
97
+ # Local cache is checked first?
98
+ !read(key, options).nil?
99
+ end
100
+
101
+ def increment(key, amount = 1) # :nodoc:
102
+ #NATIVE breaks...rufus integer prob?
103
+ # @data.incr(key, amount)
104
+ @data[key] = (@data[key].to_i + amount).to_s
105
+ end
106
+
107
+ def decrement(key, amount = 1) # :nodoc:
108
+ # @data.incr(key, -amount)
109
+ increment(key, -amount)
110
+ end
111
+
112
+ def delete_matched(matcher, options = nil) # :nodoc:
113
+ #TODO @data.ldelete?
114
+ end
115
+
116
+ def clear
117
+ @data.clear
118
+ end
119
+
120
+ def stats
121
+ @data.stat
122
+ end
123
+
124
+ private
125
+ #TODO
126
+ # def expires_in(options)
127
+ # (options && options[:expires_in]) || 0
128
+ # end
129
+
130
+ def raw?(options)
131
+ options && options[:raw]
132
+ end
133
+
134
+
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,93 @@
1
+ module Rack
2
+ module Session
3
+ class Tokyo < Abstract::ID
4
+ attr_reader :mutex, :pool
5
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :tyrant_server => "localhost:1978"
6
+
7
+ def initialize(app, options = {})
8
+ # Support old :expires option
9
+ #options[:expire_after] ||= options[:expires]
10
+ super
11
+ @mutex = Mutex.new
12
+ # @default_options = { # :namespace => 'rack:session', # }.merge(@default_options)
13
+ host, port = *@default_options[:tyrant_server].split(":") # @default_options) #options[:cache] ||
14
+ begin
15
+ @pool = Rufus::Tokyo::Tyrant.new(host, port.to_i)
16
+ rescue => e
17
+ "No server avaiable or #{e}"
18
+ end
19
+ # unless @pool.servers.any? { |s| s.alive? }
20
+ # raise "#{self} unable to find server during initialization."
21
+ # end
22
+ end
23
+
24
+ def generate_sid
25
+ loop do
26
+ sid = super
27
+ break sid unless @pool[sid]
28
+ end
29
+ end
30
+
31
+ private
32
+ def get_session(env, sid)
33
+ session = Marshal.load(@pool[sid]) if sid && sid != "" #sid ||= generate_sid
34
+ @mutex.lock if env['rack.multithread']
35
+ unless sid and session
36
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
37
+ session = {}
38
+ sid = generate_sid
39
+ ret = @pool[sid] = Marshal.dump(session)
40
+ raise "Session collision on '#{sid.inspect}'" unless ret
41
+ end
42
+ session.instance_variable_set('@old', {}.merge(session))
43
+ return [sid, session]
44
+ rescue => e
45
+ session = {}
46
+ ensure
47
+ @mutex.unlock if env['rack.multithread']
48
+ end
49
+
50
+ def set_session(env, sid, new_session, options)
51
+ @mutex.lock if env['rack.multithread']
52
+ session = Marshal.load(@pool[sid]) rescue {}
53
+ if options[:renew] or options[:drop]
54
+ @pool.delete sid
55
+ return false if options[:drop]
56
+ sid = generate_sid
57
+ @pool[sid] = 0
58
+ end
59
+ old_session = new_session.instance_variable_get('@old') || {}
60
+ session = merge_sessions sid, old_session, new_session, session
61
+ @pool[sid] = Marshal.dump(session) #, options])
62
+ return sid
63
+ rescue => e
64
+ warn "#{self} is unable to find server. #{e}"
65
+ warn $!.inspect
66
+ return false
67
+ ensure
68
+ @mutex.unlock if env['rack.multithread']
69
+ end
70
+
71
+ def merge_sessions(sid, old, new, cur=nil)
72
+ cur ||= {}
73
+ unless Hash === old and Hash === new
74
+ warn 'Bad old or new sessions provided.'
75
+ return cur
76
+ end
77
+
78
+ delete = old.keys - new.keys
79
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
80
+ delete.each{|k| cur.delete k }
81
+
82
+ update = new.keys.select{|k| new[k] != old[k] }
83
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
84
+ update.each{|k| cur[k] = new[k] }
85
+
86
+ cur
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,23 @@
1
+ require 'rufus/tokyo/tyrant'
2
+
3
+ # Rack Session
4
+ if defined?(Rack::Session)
5
+ require "rack/session/abstract/id"
6
+ require "rack/session/tokyo"
7
+ end
8
+
9
+
10
+
11
+
12
+
13
+ # # Cache store
14
+ # if defined?(Sinatra)
15
+ # require "cache/sinatra/redis_store"
16
+ # elsif defined?(Merb)
17
+ # # HACK for cyclic dependency: redis-store is required before merb-cache
18
+ # module Merb; module Cache; class AbstractStore; end end end
19
+ # require "cache/merb/redis_store"
20
+ # elsif defined?(Rails)
21
+ # require "cache/rails/redis_store"
22
+ # end
23
+
@@ -0,0 +1,252 @@
1
+ # require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # describe "TokyoStore" do
4
+ # it "should store fragment cache" do
5
+ # Rufus::Tokyo::Tyrant.should_receive(:new).and_return(@mock_tyrant = mock("Tyrant"))
6
+ # store = ActiveSupport::Cache.lookup_store :tokyo_store, "data.tch"
7
+ # store.should be_kind_of ActiveSupport::Cache::TokyoStore
8
+ # end
9
+
10
+ # it "should fail" do
11
+ # tokyo = Rufus::Tokyo::Tyrant.new('localhost', 1978)
12
+ # Rufus::Tokyo::Tyrant.should_not_receive(:new)
13
+ # store = ActiveSupport::Cache.lookup_store :tokyo_store, tokyo
14
+ # store.should be_kind_of ActiveSupport::Cache::TokyoStore
15
+ # end
16
+
17
+ # describe "Similar" do
18
+
19
+ # before(:each) do
20
+ # @cache = ActiveSupport::Cache::TokyoStore.new 'localhost:1978'
21
+ # @cache.clear
22
+ # end
23
+
24
+ # it "should return true on success" do
25
+ # @cache.write('foo', 'bar').should be_true
26
+ # end
27
+
28
+ # it "should read and write strings" do
29
+ # @cache.write('foo', 'bar')
30
+ # @cache.read('foo').should eql('bar')
31
+ # end
32
+
33
+ # it "should read and write hash" do
34
+ # @cache.write('foo', {:a => "b"})
35
+ # @cache.read('foo').should eql({:a => "b"})
36
+ # end
37
+
38
+ # it "should write integers" do
39
+ # @cache.write('foo', 1)
40
+ # @cache.read('foo').should eql(1)
41
+ # end
42
+
43
+ # it "should write nil" do
44
+ # @cache.write('foo', nil)
45
+ # @cache.read('foo').should eql(nil)
46
+ # end
47
+
48
+ # it "should have a cache miss block" do
49
+ # @cache.write('foo', 'bar')
50
+ # @cache.fetch('foo') { 'baz' }.should eql('bar')
51
+ # end
52
+
53
+ # it "should have a cache miss block" do
54
+ # @cache.fetch('foo') { 'baz' }.should eql('baz')
55
+ # end
56
+
57
+ # it "should have a forced cache miss block" do
58
+ # @cache.fetch('foo', :force => true).should be_nil
59
+ # end
60
+
61
+ # it "should read and write hash" do
62
+ # @cache.write('foo', {:a => "b", :c => "d"})
63
+ # @cache.read('foo').should eql({:a => "b", :c => "d"})
64
+ # end
65
+
66
+ # it "should read and write array" do
67
+ # @cache.write('foo', [1,2,3])
68
+ # @cache.read('foo').should eql([1,2,3])
69
+ # end
70
+
71
+ # it "should read and write obj" do
72
+ # obj = City.new; obj.name = "Acapulco"; obj.pop = 717766
73
+ # @cache.write('foo', obj)
74
+ # @cache.read('foo').should be_instance_of City
75
+ # @cache.read('foo').name.should eql("Acapulco")
76
+ # end
77
+
78
+ # it "should read multiples" do
79
+ # @cache.write('a', 1)
80
+ # @cache.write('b', 2)
81
+ # @cache.read_multi('a','b').should eql({ 'a' => 1, 'b' => 2})
82
+ # end
83
+
84
+ # it "should clear all" do
85
+ # @cache.write("erase_me", 1).should be_true
86
+ # @cache.delete("erase_me")
87
+ # @cache.exist?("erase_me").should be_false
88
+ # end
89
+
90
+ # it "should check if exists" do
91
+ # @cache.exist?("new_one").should be_false
92
+ # @cache.write("new_one", 1)
93
+ # @cache.exist?("new_one").should be_true
94
+ # end
95
+
96
+ # it "should increment value" do
97
+ # @cache.write('val', 1, :raw => true)
98
+ # @cache.read("val", :raw => true).to_i.should eql 1
99
+ # @cache.increment('val')
100
+ # @cache.read("val", :raw => true).to_i.should eql 2
101
+ # @cache.increment('val')
102
+ # @cache.read("val", :raw => true).to_i.should eql 3
103
+ # end
104
+
105
+ # it "should decrement value" do
106
+ # @cache.write('val', 3, :raw => true)
107
+ # @cache.read("val", :raw => true).to_i.should eql 3
108
+ # @cache.decrement('val')
109
+ # @cache.read("val", :raw => true).to_i.should eql 2
110
+ # @cache.decrement('val')
111
+ # @cache.read("val", :raw => true).to_i.should eql 1
112
+ # end
113
+
114
+ # it "should clear all" do
115
+ # @cache.increment("val")
116
+ # @cache.exist?("val").should be_true
117
+ # @cache.clear
118
+ # @cache.exist?("val").should be_false
119
+ # end
120
+
121
+ # it "should show some stats" do
122
+ # @cache.stats.should be_instance_of Hash #== hash_including({ :type => "hash"})
123
+ # end
124
+
125
+ # it "store objects should be immutable" do
126
+ # @cache.with_local_cache do
127
+ # @cache.write('foo', 'bar')
128
+ # @cache.read('foo').gsub!(/.*/, 'baz')# }.should raise_error(ActiveSupport::FrozenObjectError)
129
+ # @cache.read('foo').should == 'bar'
130
+ # end
131
+ # end
132
+
133
+ # it "stored objects should not be frozen" do
134
+ # @cache.with_local_cache do
135
+ # @cache.write('foo', 'bar')
136
+ # end
137
+ # @cache.with_local_cache do
138
+ # @cache.read('foo').should_not be_frozen
139
+ # end
140
+ # end
141
+
142
+ # it "should delete matched" do
143
+ # @cache.write("val", 1)
144
+ # @cache.write("value", 1)
145
+ # @cache.write("not", 1)
146
+ # @cache.delete_matched('val')
147
+ # end
148
+
149
+ # end
150
+
151
+ # describe "backed store" do
152
+ # before(:each) do
153
+ # @cache = ActiveSupport::Cache.lookup_store(:tokyo_store)
154
+ # @data = @cache.instance_variable_get(:@data)
155
+ # @cache.clear
156
+ # end
157
+
158
+ # it "local_writes_are_persistent_on_the_remote_cache" do
159
+ # @cache.with_local_cache do
160
+ # @cache.write('foo', 'bar')
161
+ # end
162
+
163
+ # @cache.read('foo').should eql('bar')
164
+ # end
165
+
166
+ # it "test_clear_also_clears_local_cache" do
167
+ # @cache.with_local_cache do
168
+ # @cache.write('foo', 'bar')
169
+ # @cache.clear
170
+ # @cache.read('foo').should be_nil
171
+ # end
172
+ # end
173
+
174
+ # it "test_local_cache_of_read_and_write" do
175
+ # @cache.with_local_cache do
176
+ # @cache.write('foo', 'bar')
177
+ # @data.clear # Clear remote cache
178
+ # @cache.read('foo').should eql('bar')
179
+ # end
180
+ # end
181
+
182
+ # it "test_local_cache_should_read_and_write_integer" do
183
+ # @cache.with_local_cache do
184
+ # @cache.write('foo', 1)
185
+ # @cache.read('foo').should eql(1)
186
+ # end
187
+ # end
188
+
189
+ # it "test_local_cache_of_delete" do
190
+ # @cache.with_local_cache do
191
+ # @cache.write('foo', 'bar')
192
+ # @cache.delete('foo')
193
+ # @data.clear # Clear remote cache
194
+ # @cache.read('foo').should be_nil
195
+ # end
196
+ # end
197
+
198
+ # it "test_local_cache_of_exist" do
199
+ # @cache.with_local_cache do
200
+ # @cache.write('foo', 'bar')
201
+ # @cache.instance_variable_set(:@data, nil)
202
+ # @data.clear # Clear remote cache
203
+ # @cache.exist?('foo').should be_true
204
+ # end
205
+ # end
206
+
207
+ # it "test_local_cache_of_increment" do
208
+ # @cache.with_local_cache do
209
+ # @cache.write('foo', 1, :raw => true)
210
+ # @cache.increment('foo')
211
+ # @data.clear # Clear remote cache
212
+ # @cache.read('foo', :raw => true).to_i.should eql(2)
213
+ # end
214
+ # end
215
+
216
+ # it "test_local_cache_of_decrement" do
217
+ # @cache.with_local_cache do
218
+ # @cache.write('foo', 1, :raw => true)
219
+ # @cache.decrement('foo')
220
+ # @data.clear # Clear remote cache
221
+ # @cache.read('foo', :raw => true).to_i.should be_zero
222
+ # end
223
+ # end
224
+
225
+ # it "test_exist_with_nulls_cached_locally" do
226
+ # @cache.with_local_cache do
227
+ # @cache.write('foo', 'bar')
228
+ # @cache.delete('foo')
229
+ # @cache.exist?('foo').should be_false
230
+ # end
231
+ # end
232
+
233
+ # it "test_multi_get" do
234
+ # @cache.with_local_cache do
235
+ # @cache.write('foo', 1)
236
+ # @cache.write('goo', 2)
237
+ # @cache.read_multi('foo', 'goo').should eql({'foo' => 1, 'goo' => 2})
238
+ # end
239
+ # end
240
+
241
+ # it "test_middleware" do
242
+ # app = lambda { |env|
243
+ # result = @cache.write('foo', 'bar')
244
+ # @cache.read('foo').should eql('bar') # make sure 'foo' was written
245
+ # }
246
+ # app = @cache.middleware.new(app)
247
+ # app.call({})
248
+ # end
249
+
250
+ # end
251
+
252
+ # end
@@ -0,0 +1,238 @@
1
+ # Code from http://github.com/jodosha/redis-store
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ module Rack
5
+ module Session
6
+ describe "Rack::Session::Tokyo" do
7
+ before(:each) do
8
+ @session_key = Rack::Session::Tokyo::DEFAULT_OPTIONS[:key]
9
+ @session_match = /#{@session_key}=[0-9a-fA-F]+;/
10
+ @incrementor = lambda do |env|
11
+ env["rack.session"]["counter"] ||= 0
12
+ env["rack.session"]["counter"] += 1
13
+ Rack::Response.new(env["rack.session"].inspect).to_a
14
+ end
15
+ @drop_session = proc do |env|
16
+ env['rack.session.options'][:drop] = true
17
+ @incrementor.call(env)
18
+ end
19
+ @renew_session = proc do |env|
20
+ env['rack.session.options'][:renew] = true
21
+ @incrementor.call(env)
22
+ end
23
+ @defer_session = proc do |env|
24
+ env['rack.session.options'][:defer] = true
25
+ @incrementor.call(env)
26
+ end
27
+ end
28
+
29
+ it "should specify connection params" do
30
+ pool = Rack::Session::Tokyo.new(@incrementor, :tokyo_server => "localhost:6380/1").pool
31
+ pool.should be_kind_of(Rufus::Tokyo::Tyrant)
32
+ pool.host.should == "localhost"
33
+ pool.port.should == 1978
34
+
35
+ # pool = Rack::Session::Tokyo.new(@incrementor, :tokyo_server => ["localhost:6379", "localhost:6380"]).pool
36
+ # pool.should be_kind_of(DistributedMarshaledTokyo)
37
+ end
38
+
39
+ it "creates a new cookie" do
40
+ pool = Rack::Session::Tokyo.new(@incrementor)
41
+ res = Rack::MockRequest.new(pool).get("/")
42
+ res["Set-Cookie"].should match(/#{@session_key}=/)
43
+ res.body.should == '{"counter"=>1}'
44
+ end
45
+
46
+ it "determines session from a cookie" do
47
+ pool = Rack::Session::Tokyo.new(@incrementor)
48
+ req = Rack::MockRequest.new(pool)
49
+ res = req.get("/")
50
+ cookie = res["Set-Cookie"]
51
+ req.get("/", "HTTP_COOKIE" => cookie).
52
+ body.should == '{"counter"=>2}'
53
+ req.get("/", "HTTP_COOKIE" => cookie).
54
+ body.should == '{"counter"=>3}'
55
+ end
56
+
57
+ it "survives nonexistant cookies" do
58
+ bad_cookie = "rack.session=blarghfasel"
59
+ pool = Rack::Session::Tokyo.new(@incrementor)
60
+ res = Rack::MockRequest.new(pool).
61
+ get("/", "HTTP_COOKIE" => bad_cookie)
62
+ res.body.should == '{"counter"=>1}'
63
+ cookie = res["Set-Cookie"][@session_match]
64
+ cookie.should_not match(/#{bad_cookie}/)
65
+ end
66
+
67
+ it "should maintain freshness" do
68
+ pool = Rack::Session::Tokyo.new(@incrementor, :expire_after => 3)
69
+ res = Rack::MockRequest.new(pool).get('/')
70
+ res.body.should include('"counter"=>1')
71
+ cookie = res["Set-Cookie"]
72
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
73
+ res["Set-Cookie"].should == cookie
74
+ res.body.should include('"counter"=>2')
75
+ puts 'Sleeping to expire session' if $DEBUG
76
+ sleep 4
77
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
78
+ res["Set-Cookie"].should_not == cookie
79
+ res.body.should include('"counter"=>1')
80
+ end
81
+
82
+ it "deletes cookies with :drop option" do
83
+ pool = Rack::Session::Tokyo.new(@incrementor)
84
+ req = Rack::MockRequest.new(pool)
85
+ drop = Rack::Utils::Context.new(pool, @drop_session)
86
+ dreq = Rack::MockRequest.new(drop)
87
+
88
+ res0 = req.get("/")
89
+ session = (cookie = res0["Set-Cookie"])[@session_match]
90
+ res0.body.should == '{"counter"=>1}'
91
+
92
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
93
+ res1["Set-Cookie"][@session_match].should == session
94
+ res1.body.should == '{"counter"=>2}'
95
+
96
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
97
+ res2["Set-Cookie"].should be_nil
98
+ res2.body.should == '{"counter"=>3}'
99
+
100
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
101
+ res3["Set-Cookie"][@session_match].should_not == session
102
+ res3.body.should == '{"counter"=>1}'
103
+ end
104
+
105
+ it "provides new session id with :renew option" do
106
+ pool = Rack::Session::Tokyo.new(@incrementor)
107
+ req = Rack::MockRequest.new(pool)
108
+ renew = Rack::Utils::Context.new(pool, @renew_session)
109
+ rreq = Rack::MockRequest.new(renew)
110
+
111
+ res0 = req.get("/")
112
+ session = (cookie = res0["Set-Cookie"])[@session_match]
113
+ res0.body.should == '{"counter"=>1}'
114
+
115
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
116
+ res1["Set-Cookie"][@session_match].should == session
117
+ res1.body.should == '{"counter"=>2}'
118
+
119
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
120
+ new_cookie = res2["Set-Cookie"]
121
+ new_session = new_cookie[@session_match]
122
+ new_session.should_not == session
123
+ res2.body.should == '{"counter"=>3}'
124
+
125
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
126
+ res3["Set-Cookie"][@session_match].should == new_session
127
+ res3.body.should == '{"counter"=>4}'
128
+ end
129
+
130
+ specify "omits cookie with :defer option" do
131
+ pool = Rack::Session::Tokyo.new(@incrementor)
132
+ req = Rack::MockRequest.new(pool)
133
+ defer = Rack::Utils::Context.new(pool, @defer_session)
134
+ dreq = Rack::MockRequest.new(defer)
135
+
136
+ res0 = req.get("/")
137
+ session = (cookie = res0["Set-Cookie"])[@session_match]
138
+ res0.body.should == '{"counter"=>1}'
139
+
140
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
141
+ res1["Set-Cookie"][@session_match].should == session
142
+ res1.body.should == '{"counter"=>2}'
143
+
144
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
145
+ res2["Set-Cookie"].should be_nil
146
+ res2.body.should == '{"counter"=>3}'
147
+
148
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
149
+ res3["Set-Cookie"][@session_match].should == session
150
+ res3.body.should == '{"counter"=>4}'
151
+ end
152
+
153
+ # anyone know how to do this better?
154
+ specify "multithread: should cleanly merge sessions" do
155
+ next unless $DEBUG
156
+ warn 'Running multithread test for Session::Tokyo'
157
+ pool = Rack::Session::Tokyo.new(@incrementor)
158
+ req = Rack::MockRequest.new(pool)
159
+
160
+ res = req.get('/')
161
+ res.body.should == '{"counter"=>1}'
162
+ cookie = res["Set-Cookie"]
163
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
164
+
165
+ delta_incrementor = lambda do |env|
166
+ # emulate disconjoinment of threading
167
+ env['rack.session'] = env['rack.session'].dup
168
+ Thread.stop
169
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
170
+ @incrementor.call(env)
171
+ end
172
+ tses = Rack::Utils::Context.new pool, delta_incrementor
173
+ treq = Rack::MockRequest.new(tses)
174
+ tnum = rand(7).to_i+5
175
+ r = Array.new(tnum) do
176
+ Thread.new(treq) do |run|
177
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
178
+ end
179
+ end.reverse.map{|t| t.run.join.value }
180
+ r.each do |res|
181
+ res['Set-Cookie'].should == cookie
182
+ res.body.should include('"counter"=>2')
183
+ end
184
+
185
+ session = pool.pool.get(sess_id)
186
+ session.size.should == tnum+1 # counter
187
+ session['counter'].should == 2 # meeeh
188
+
189
+ tnum = rand(7).to_i+5
190
+ r = Array.new(tnum) do |i|
191
+ delta_time = proc do |env|
192
+ env['rack.session'][i] = Time.now
193
+ Thread.stop
194
+ env['rack.session'] = env['rack.session'].dup
195
+ env['rack.session'][i] -= Time.now
196
+ @incrementor.call(env)
197
+ end
198
+ app = Rack::Utils::Context.new pool, time_delta
199
+ req = Rack::MockRequest.new app
200
+ Thread.new(req) do |run|
201
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
202
+ end
203
+ end.reverse.map{|t| t.run.join.value }
204
+ r.each do |res|
205
+ res['Set-Cookie'].should == cookie
206
+ res.body.should include('"counter"=>3')
207
+ end
208
+
209
+ session = pool.pool.get(sess_id)
210
+ session.size.should == tnum+1
211
+ session['counter'].should == 3
212
+
213
+ drop_counter = proc do |env|
214
+ env['rack.session'].delete 'counter'
215
+ env['rack.session']['foo'] = 'bar'
216
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
217
+ end
218
+ tses = Rack::Utils::Context.new pool, drop_counter
219
+ treq = Rack::MockRequest.new(tses)
220
+ tnum = rand(7).to_i+5
221
+ r = Array.new(tnum) do
222
+ Thread.new(treq) do |run|
223
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
224
+ end
225
+ end.reverse.map{|t| t.run.join.value }
226
+ r.each do |res|
227
+ res['Set-Cookie'].should == cookie
228
+ res.body.should include('"foo"=>"bar"')
229
+ end
230
+
231
+ session = pool.pool.get(sess_id)
232
+ session.size.should == r.size+1
233
+ session['counter'].should be_nil
234
+ session['foo'].should == 'bar'
235
+ end
236
+ end
237
+ end
238
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --loadby mtime
3
+ --reverse
@@ -0,0 +1,33 @@
1
+ $: << File.join(File.dirname(__FILE__), "/../lib")
2
+ require 'rubygems'
3
+ require 'spec'
4
+ require 'rack'
5
+ # $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ # $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ require 'tokyo_store'
8
+ require 'rack/session/tokyo'
9
+ #require 'rack/cache/tokyo'
10
+ #ENV["RAILS_ENV"] = "test"
11
+ require 'activesupport'
12
+ require 'active_support'
13
+ require 'actionpack'
14
+ require 'action_controller'
15
+ # require 'action_controller/test_process'
16
+ # require 'action_pack'
17
+
18
+ # ActionController::Base.session_store = :tokyo_store
19
+ # #ActionController::Base.ignore_missing_templates = true
20
+
21
+ # DispatcherApp = ActionController::Dispatcher.new
22
+ # TokyoStoreStoreApp = ActionController::Session::TokyoStore.new(
23
+ # DispatcherApp, :key => '_s_id')
24
+
25
+ #Simple class to test marshal
26
+ class City
27
+ attr_accessor :name, :pop
28
+ end
29
+ #require 'spec/rails'
30
+
31
+ Spec::Runner.configure do |config|
32
+
33
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ if ENV['CABINET']
4
+ describe "TokyoStore" do
5
+ it "should store fragment cache" do
6
+ HDB.should_receive(:new).and_return(@mock_hdb = mock("HDB"))
7
+ @mock_hdb.should_receive(:open).with('data.tch', 6).and_return(true)
8
+ store = ActiveSupport::Cache.lookup_store :tokyo_store, "data.tch"
9
+ store.should be_kind_of ActiveSupport::Cache::TokyoStore
10
+ end
11
+
12
+ it "should fail" do
13
+ tokyo = HDB.new
14
+ tokyo.open('data.tch')
15
+ HDB.should_not_receive(:new)
16
+ store = ActiveSupport::Cache.lookup_store :tokyo_store, tokyo
17
+ store.should be_kind_of ActiveSupport::Cache::TokyoStore
18
+ end
19
+
20
+ describe "Similar" do
21
+
22
+ before(:all) do
23
+ @cache = ActiveSupport::Cache::TokyoStore.new 'data.tcb'
24
+ end
25
+
26
+ it "test_should_read_and_write_strings" do
27
+ @cache.write('foo', 'bar')
28
+ @cache.read('foo').should eql('bar')
29
+ end
30
+
31
+ it "test_should_read_and_write_hash" do
32
+ @cache.write('foo', {:a => "b"})
33
+ @cache.read('foo').should eql({:a => "b"})
34
+ end
35
+
36
+ it "test_should_read_and_write_hash" do
37
+ @cache.write('foo', {:a => "b", :c => "d"})
38
+ @cache.read('foo').should eql({:a => "b", :c => "d"})
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{tokyo_store}
5
+ s.version = "0.1.7"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Marcos Piccinini"]
9
+ s.date = %q{2009-07-03}
10
+ s.email = %q{x@nofxx.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "benchmark/tokyo_store.rb",
23
+ "lib/rack/cache/tokyo_cache_store.rb",
24
+ "lib/rack/session/tokyo.rb",
25
+ "lib/tokyo_store.rb",
26
+ "spec/rack/cache/tokyo_cache_spec.rb",
27
+ "spec/rack/session/tokyo_spec.rb",
28
+ "spec/spec.opts",
29
+ "spec/spec_helper.rb",
30
+ "spec/tokyo_store_spec.rb",
31
+ "tokyo_store.gemspec"
32
+ ]
33
+ s.homepage = %q{http://github.com/nofxx/tokyo_store}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.4}
37
+ s.summary = %q{Tokyo Tyrant rails session store}
38
+ s.test_files = [
39
+ "spec/rack/cache/tokyo_cache_spec.rb",
40
+ "spec/rack/session/tokyo_spec.rb",
41
+ "spec/tokyo_store_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nofxx-tokyo_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - Marcos Piccinini
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: x@nofxx.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION
32
+ - benchmark/tokyo_store.rb
33
+ - lib/rack/cache/tokyo_cache_store.rb
34
+ - lib/rack/session/tokyo.rb
35
+ - lib/tokyo_store.rb
36
+ - spec/rack/cache/tokyo_cache_spec.rb
37
+ - spec/rack/session/tokyo_spec.rb
38
+ - spec/spec.opts
39
+ - spec/spec_helper.rb
40
+ - spec/tokyo_store_spec.rb
41
+ - tokyo_store.gemspec
42
+ has_rdoc: false
43
+ homepage: http://github.com/nofxx/tokyo_store
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.2.0
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Tokyo Tyrant rails session store
68
+ test_files:
69
+ - spec/rack/cache/tokyo_cache_spec.rb
70
+ - spec/rack/session/tokyo_spec.rb
71
+ - spec/tokyo_store_spec.rb
72
+ - spec/spec_helper.rb