redis-store 1.0.0.beta2 → 1.0.0.beta3
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.
Potentially problematic release.
This version of redis-store might be problematic. Click here for more details.
- data/CHANGELOG +74 -23
- data/Gemfile +12 -7
- data/Gemfile.lock +183 -0
- data/MIT-LICENSE +1 -1
- data/README.md +49 -14
- data/Rakefile +8 -4
- data/VERSION +1 -1
- data/lib/{rack/session/rails.rb → action_controller/session/redis_session_store.rb} +24 -20
- data/lib/{cache/rails → active_support/cache}/redis_store.rb +64 -22
- data/lib/cache/merb/redis_store.rb +20 -8
- data/lib/cache/sinatra/redis_store.rb +20 -8
- data/lib/i18n/backend/redis.rb +67 -0
- data/lib/rack/cache/redis_metastore.rb +4 -4
- data/lib/rack/session/redis.rb +7 -7
- data/lib/redis-store.rb +16 -14
- data/lib/redis/distributed_store.rb +35 -0
- data/lib/redis/factory.rb +29 -10
- data/lib/redis/store.rb +30 -0
- data/lib/redis/store/interface.rb +17 -0
- data/lib/redis/store/marshalling.rb +41 -0
- data/lib/redis/store/namespace.rb +54 -0
- data/lib/redis/store/ttl.rb +37 -0
- data/lib/redis/store/version.rb +12 -0
- data/redis-store.gemspec +32 -20
- data/spec/action_controller/session/redis_session_store_spec.rb +121 -0
- data/spec/{cache/rails → active_support/cache}/redis_store_spec.rb +93 -19
- data/spec/cache/merb/redis_store_spec.rb +14 -11
- data/spec/cache/sinatra/redis_store_spec.rb +14 -11
- data/spec/config/master.conf +1 -1
- data/spec/config/single.conf +1 -1
- data/spec/config/slave.conf +1 -1
- data/spec/i18n/backend/redis_spec.rb +56 -0
- data/spec/rack/cache/entitystore/redis_spec.rb +10 -8
- data/spec/rack/cache/metastore/redis_spec.rb +2 -2
- data/spec/rack/session/redis_spec.rb +6 -6
- data/spec/redis/distributed_store_spec.rb +47 -0
- data/spec/redis/factory_spec.rb +58 -16
- data/spec/redis/store/interface_spec.rb +23 -0
- data/spec/redis/store/marshalling_spec.rb +83 -0
- data/spec/redis/store/namespace_spec.rb +76 -0
- data/spec/redis/store/version_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -5
- data/tasks/redis.tasks.rb +19 -12
- metadata +33 -21
- data/lib/redis/distributed_marshaled.rb +0 -28
- data/lib/redis/marshaled_client.rb +0 -65
- data/lib/redis_store/version.rb +0 -10
- data/spec/rack/session/redis_session_store_spec.rb +0 -75
- data/spec/redis/distributed_marshaled_redis_spec.rb +0 -33
- data/spec/redis/marshaled_client_spec.rb +0 -83
- data/spec/redis_store/version_spec.rb +0 -7
data/lib/rack/session/redis.rb
CHANGED
@@ -2,7 +2,7 @@ module Rack
|
|
2
2
|
module Session
|
3
3
|
class Redis < Abstract::ID
|
4
4
|
attr_reader :mutex, :pool
|
5
|
-
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :redis_server => "
|
5
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :redis_server => "redis://127.0.0.1:6379/0"
|
6
6
|
|
7
7
|
def initialize(app, options = {})
|
8
8
|
super
|
@@ -13,18 +13,18 @@ module Rack
|
|
13
13
|
def generate_sid
|
14
14
|
loop do
|
15
15
|
sid = super
|
16
|
-
break sid unless @pool.
|
16
|
+
break sid unless @pool.get(sid)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
def get_session(env, sid)
|
21
|
-
session = @pool.
|
21
|
+
session = @pool.get(sid) if sid
|
22
22
|
@mutex.lock if env['rack.multithread']
|
23
23
|
unless sid and session
|
24
24
|
env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
|
25
25
|
session = {}
|
26
26
|
sid = generate_sid
|
27
|
-
ret = @pool.
|
27
|
+
ret = @pool.set sid, session
|
28
28
|
raise "Session collision on '#{sid.inspect}'" unless ret
|
29
29
|
end
|
30
30
|
session.instance_variable_set('@old', {}.merge(session))
|
@@ -39,16 +39,16 @@ module Rack
|
|
39
39
|
|
40
40
|
def set_session(env, session_id, new_session, options)
|
41
41
|
@mutex.lock if env['rack.multithread']
|
42
|
-
session = @pool.
|
42
|
+
session = @pool.get(session_id) rescue {}
|
43
43
|
if options[:renew] or options[:drop]
|
44
44
|
@pool.del session_id
|
45
45
|
return false if options[:drop]
|
46
46
|
session_id = generate_sid
|
47
|
-
@pool.
|
47
|
+
@pool.set session_id, 0
|
48
48
|
end
|
49
49
|
old_session = new_session.instance_variable_get('@old') || {}
|
50
50
|
session = merge_sessions session_id, old_session, new_session, session
|
51
|
-
@pool.
|
51
|
+
@pool.set session_id, session, options
|
52
52
|
return session_id
|
53
53
|
rescue Errno::ECONNREFUSED
|
54
54
|
warn "#{self} is unable to find server."
|
data/lib/redis-store.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
require "redis"
|
2
2
|
require "redis/distributed"
|
3
3
|
require "redis/factory"
|
4
|
-
require "redis/
|
5
|
-
require "redis/
|
6
|
-
require "
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
4
|
+
require "redis/store/interface"
|
5
|
+
require "redis/store/ttl"
|
6
|
+
require "redis/store/namespace"
|
7
|
+
require "redis/store/marshalling"
|
8
|
+
require "redis/store/version"
|
9
|
+
require "redis/store"
|
10
|
+
require "redis/distributed_store"
|
13
11
|
|
14
12
|
# Cache store
|
15
13
|
if defined?(Sinatra)
|
@@ -18,8 +16,6 @@ elsif defined?(Merb)
|
|
18
16
|
# HACK for cyclic dependency: redis-store is required before merb-cache
|
19
17
|
module Merb; module Cache; class AbstractStore; end end end
|
20
18
|
require "cache/merb/redis_store"
|
21
|
-
elsif defined?(Rails)
|
22
|
-
require "cache/rails/redis_store"
|
23
19
|
end
|
24
20
|
|
25
21
|
# Rack::Session
|
@@ -29,9 +25,11 @@ if defined?(Rack::Session)
|
|
29
25
|
if defined?(Merb)
|
30
26
|
require "rack/session/merb"
|
31
27
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
# ActionDispatch::Session
|
31
|
+
if ::Redis::Store.rails3?
|
32
|
+
require "action_controller/session/redis_session_store"
|
35
33
|
end
|
36
34
|
|
37
35
|
# Rack::Cache
|
@@ -40,3 +38,7 @@ if defined?(Rack::Cache)
|
|
40
38
|
require "rack/cache/redis_metastore"
|
41
39
|
require "rack/cache/redis_entitystore"
|
42
40
|
end
|
41
|
+
|
42
|
+
if defined?(I18n)
|
43
|
+
require "i18n/backend/redis"
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Redis
|
2
|
+
class DistributedStore < Distributed
|
3
|
+
attr_reader :ring
|
4
|
+
|
5
|
+
def initialize(addresses, options = { })
|
6
|
+
nodes = addresses.map do |address|
|
7
|
+
::Redis::Store.new address
|
8
|
+
end
|
9
|
+
_extend_namespace options
|
10
|
+
@ring = Redis::HashRing.new nodes
|
11
|
+
end
|
12
|
+
|
13
|
+
def nodes
|
14
|
+
ring.nodes
|
15
|
+
end
|
16
|
+
|
17
|
+
def set(key, value, options = nil)
|
18
|
+
node_for(key).set(key, value, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(key, options = nil)
|
22
|
+
node_for(key).get(key, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def setnx(key, value, options = nil)
|
26
|
+
node_for(key).setnx(key, value, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def _extend_namespace(options)
|
31
|
+
@namespace = options[:namespace]
|
32
|
+
extend ::Redis::Store::Namespace if @namespace
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/redis/factory.rb
CHANGED
@@ -6,21 +6,40 @@ class Redis
|
|
6
6
|
result
|
7
7
|
end
|
8
8
|
if redis_client_options.size > 1
|
9
|
-
|
9
|
+
::Redis::DistributedStore.new redis_client_options
|
10
10
|
else
|
11
|
-
|
11
|
+
::Redis::Store.new redis_client_options.first || {}
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.convert_to_redis_client_options(address_or_options)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
if address_or_options.is_a?(Hash)
|
17
|
+
options = address_or_options.dup
|
18
|
+
options[:namespace] ||= options.delete(:key_prefix) # RailsSessionStore
|
19
|
+
options
|
20
|
+
else
|
21
|
+
if address_or_options =~ /redis\:\/\//
|
22
|
+
require 'uri'
|
23
|
+
uri = URI.parse address_or_options
|
24
|
+
_, db, namespace = if uri.path
|
25
|
+
uri.path.split /\//
|
26
|
+
end
|
27
|
+
else
|
28
|
+
warn "[DEPRECATION] `#{address_or_options}` is deprecated. Please use `redis://#{address_or_options}` instead."
|
29
|
+
address_or_options, password = address_or_options.split(/\@/).reverse
|
30
|
+
password = password.gsub(/\:/, "") if password
|
31
|
+
host, port = address_or_options.split /\:/
|
32
|
+
port, db, namespace = port.split /\// if port
|
33
|
+
end
|
34
|
+
|
35
|
+
options = {}
|
36
|
+
options[:host] = host || uri.host
|
37
|
+
options[:port] = port || uri.port
|
38
|
+
options[:db] = db.to_i if db
|
39
|
+
options[:namespace] = namespace if namespace
|
40
|
+
options[:password] = password || uri.password
|
41
|
+
options
|
42
|
+
end
|
24
43
|
end
|
25
44
|
end
|
26
45
|
end
|
data/lib/redis/store.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
include Ttl, Interface
|
4
|
+
|
5
|
+
def initialize(options = { })
|
6
|
+
super
|
7
|
+
_extend_marshalling options
|
8
|
+
_extend_namespace options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.rails3? #:nodoc:
|
12
|
+
defined?(::Rails) && ::Rails.version =~ /3\.0\.0/
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"Redis Client connected to #{@client.host}:#{@client.port} against DB #{@client.db}"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def _extend_marshalling(options)
|
21
|
+
@marshalling = !(options[:marshalling] === false) # HACK - TODO delegate to Factory
|
22
|
+
extend Marshalling if @marshalling
|
23
|
+
end
|
24
|
+
|
25
|
+
def _extend_namespace(options)
|
26
|
+
@namespace = options[:namespace]
|
27
|
+
extend Namespace if @namespace
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Interface
|
4
|
+
def get(key, options = nil)
|
5
|
+
super(key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def set(key, value, options = nil)
|
9
|
+
super(key, value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def setnx(key, value, options = nil)
|
13
|
+
super(key, value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Marshalling
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
_marshal(value, options) { |value| super key, value, options }
|
6
|
+
end
|
7
|
+
|
8
|
+
def setnx(key, value, options = nil)
|
9
|
+
_marshal(value, options) { |value| super key, value, options }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(key, options = nil)
|
13
|
+
_unmarshal super(key), options
|
14
|
+
end
|
15
|
+
|
16
|
+
def mget(*keys)
|
17
|
+
options = keys.flatten.pop if keys.flatten.last.is_a?(Hash)
|
18
|
+
super(*keys).map do |result|
|
19
|
+
_unmarshal result, options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def _marshal(val, options)
|
25
|
+
yield marshal?(options) ? val : Marshal.dump(val)
|
26
|
+
end
|
27
|
+
|
28
|
+
def _unmarshal(val, options)
|
29
|
+
unmarshal?(val, options) ? Marshal.load(val) : val
|
30
|
+
end
|
31
|
+
|
32
|
+
def marshal?(options)
|
33
|
+
options && options[:raw]
|
34
|
+
end
|
35
|
+
|
36
|
+
def unmarshal?(result, options)
|
37
|
+
result && result.size > 0 && !marshal?(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Namespace
|
4
|
+
def set(key, val, options = nil)
|
5
|
+
namespace(key) { |key| super(key, val, options) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def setnx(key, val, options = nil)
|
9
|
+
namespace(key) { |key| super(key, val, options) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(key, options = nil)
|
13
|
+
namespace(key) { |key| super(key, options) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists(key)
|
17
|
+
namespace(key) { |key| super(key) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def incrby(key, increment)
|
21
|
+
namespace(key) { |key| super(key, increment) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def decrby(key, increment)
|
25
|
+
namespace(key) { |key| super(key, increment) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def keys(pattern = "*")
|
29
|
+
namespace(pattern) { |pattern| super(pattern) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def del(*keys)
|
33
|
+
super *keys.map {|key| interpolate(key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def mget(*keys)
|
37
|
+
super *keys.map {|key| interpolate(key) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"#{super} with namespace #{@namespace}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def namespace(key)
|
46
|
+
yield interpolate(key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def interpolate(key)
|
50
|
+
key.match(%r{^#{@namespace}\:}) ? key : "#{@namespace}:#{key}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Ttl
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
if ttl = expires_in(options)
|
6
|
+
setex(key, ttl, value)
|
7
|
+
else
|
8
|
+
super(key, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def setnx(key, value, options = nil)
|
13
|
+
if ttl = expires_in(options)
|
14
|
+
setnx_with_expire(key, value, ttl)
|
15
|
+
else
|
16
|
+
super(key, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def setnx_with_expire(key, value, ttl)
|
22
|
+
multi do
|
23
|
+
setnx(key, value)
|
24
|
+
expire(key, expires_in)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def expires_in(options)
|
30
|
+
if options
|
31
|
+
# Rack::Session Merb Rails/Sinatra
|
32
|
+
options[:expire_after] || options[:expires_in] || options[:expire_in]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/redis-store.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{redis-store}
|
8
|
-
s.version = "1.0.0.
|
8
|
+
s.version = "1.0.0.beta3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Luca Guidi"]
|
12
|
-
s.date = %q{2010-
|
13
|
-
s.description = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
|
12
|
+
s.date = %q{2010-09-10}
|
13
|
+
s.description = %q{Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks.}
|
14
14
|
s.email = %q{guidi.luca@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"README.md"
|
@@ -19,39 +19,48 @@ Gem::Specification.new do |s|
|
|
19
19
|
".gitignore",
|
20
20
|
"CHANGELOG",
|
21
21
|
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
22
23
|
"MIT-LICENSE",
|
23
24
|
"README.md",
|
24
25
|
"Rakefile",
|
25
26
|
"VERSION",
|
27
|
+
"lib/action_controller/session/redis_session_store.rb",
|
28
|
+
"lib/active_support/cache/redis_store.rb",
|
26
29
|
"lib/cache/merb/redis_store.rb",
|
27
|
-
"lib/cache/rails/redis_store.rb",
|
28
30
|
"lib/cache/sinatra/redis_store.rb",
|
31
|
+
"lib/i18n/backend/redis.rb",
|
29
32
|
"lib/rack/cache/redis_entitystore.rb",
|
30
33
|
"lib/rack/cache/redis_metastore.rb",
|
31
34
|
"lib/rack/session/merb.rb",
|
32
|
-
"lib/rack/session/rails.rb",
|
33
35
|
"lib/rack/session/redis.rb",
|
34
36
|
"lib/redis-store.rb",
|
35
|
-
"lib/redis/
|
37
|
+
"lib/redis/distributed_store.rb",
|
36
38
|
"lib/redis/factory.rb",
|
37
|
-
"lib/redis/
|
38
|
-
"lib/
|
39
|
+
"lib/redis/store.rb",
|
40
|
+
"lib/redis/store/interface.rb",
|
41
|
+
"lib/redis/store/marshalling.rb",
|
42
|
+
"lib/redis/store/namespace.rb",
|
43
|
+
"lib/redis/store/ttl.rb",
|
44
|
+
"lib/redis/store/version.rb",
|
39
45
|
"redis-store.gemspec",
|
46
|
+
"spec/action_controller/session/redis_session_store_spec.rb",
|
47
|
+
"spec/active_support/cache/redis_store_spec.rb",
|
40
48
|
"spec/cache/merb/redis_store_spec.rb",
|
41
|
-
"spec/cache/rails/redis_store_spec.rb",
|
42
49
|
"spec/cache/sinatra/redis_store_spec.rb",
|
43
50
|
"spec/config/master.conf",
|
44
51
|
"spec/config/single.conf",
|
45
52
|
"spec/config/slave.conf",
|
53
|
+
"spec/i18n/backend/redis_spec.rb",
|
46
54
|
"spec/rack/cache/entitystore/pony.jpg",
|
47
55
|
"spec/rack/cache/entitystore/redis_spec.rb",
|
48
56
|
"spec/rack/cache/metastore/redis_spec.rb",
|
49
|
-
"spec/rack/session/redis_session_store_spec.rb",
|
50
57
|
"spec/rack/session/redis_spec.rb",
|
51
|
-
"spec/redis/
|
58
|
+
"spec/redis/distributed_store_spec.rb",
|
52
59
|
"spec/redis/factory_spec.rb",
|
53
|
-
"spec/redis/
|
54
|
-
"spec/
|
60
|
+
"spec/redis/store/interface_spec.rb",
|
61
|
+
"spec/redis/store/marshalling_spec.rb",
|
62
|
+
"spec/redis/store/namespace_spec.rb",
|
63
|
+
"spec/redis/store/version_spec.rb",
|
55
64
|
"spec/spec_helper.rb",
|
56
65
|
"tasks/redis.tasks.rb"
|
57
66
|
]
|
@@ -59,19 +68,22 @@ Gem::Specification.new do |s|
|
|
59
68
|
s.rdoc_options = ["--charset=UTF-8"]
|
60
69
|
s.require_paths = ["lib"]
|
61
70
|
s.rubygems_version = %q{1.3.7}
|
62
|
-
s.summary = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
|
71
|
+
s.summary = %q{Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks.}
|
63
72
|
s.test_files = [
|
64
|
-
"spec/
|
65
|
-
"spec/cache/
|
73
|
+
"spec/action_controller/session/redis_session_store_spec.rb",
|
74
|
+
"spec/active_support/cache/redis_store_spec.rb",
|
75
|
+
"spec/cache/merb/redis_store_spec.rb",
|
66
76
|
"spec/cache/sinatra/redis_store_spec.rb",
|
77
|
+
"spec/i18n/backend/redis_spec.rb",
|
67
78
|
"spec/rack/cache/entitystore/redis_spec.rb",
|
68
79
|
"spec/rack/cache/metastore/redis_spec.rb",
|
69
|
-
"spec/rack/session/redis_session_store_spec.rb",
|
70
80
|
"spec/rack/session/redis_spec.rb",
|
71
|
-
"spec/redis/
|
81
|
+
"spec/redis/distributed_store_spec.rb",
|
72
82
|
"spec/redis/factory_spec.rb",
|
73
|
-
"spec/redis/
|
74
|
-
"spec/
|
83
|
+
"spec/redis/store/interface_spec.rb",
|
84
|
+
"spec/redis/store/marshalling_spec.rb",
|
85
|
+
"spec/redis/store/namespace_spec.rb",
|
86
|
+
"spec/redis/store/version_spec.rb",
|
75
87
|
"spec/spec_helper.rb"
|
76
88
|
]
|
77
89
|
|