reconfig 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/LICENSE.md +19 -0
- data/README.md +2 -0
- data/lib/reconfig.rb +75 -0
- data/lib/reconfig/configuration.rb +17 -0
- data/lib/reconfig/namespace.rb +68 -0
- data/lib/reconfig/redis_client.rb +66 -0
- data/lib/reconfig/type_mapper.rb +53 -0
- data/lib/reconfig/version.rb +18 -0
- data/reconfig.gemspec +21 -0
- data/spec/reconfig/configuration_spec.rb +31 -0
- data/spec/reconfig/namespace_spec.rb +65 -0
- data/spec/reconfig/redis_client_spec.rb +127 -0
- data/spec/reconfig/type_mapper_spec.rb +41 -0
- data/spec/reconfig_spec.rb +69 -0
- data/spec/spec_helper.rb +4 -0
- metadata +82 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 Chris Maddox
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/lib/reconfig.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'reconfig/type_mapper'
|
4
|
+
require 'reconfig/redis_client'
|
5
|
+
require 'reconfig/namespace'
|
6
|
+
require 'reconfig/configuration'
|
7
|
+
|
8
|
+
module Reconfig
|
9
|
+
class << self
|
10
|
+
extend Forwardable
|
11
|
+
delegate [:prefix, :meta_key] => :configuration
|
12
|
+
|
13
|
+
def configure
|
14
|
+
yield configuration if block_given?
|
15
|
+
apply_configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](namespace)
|
19
|
+
namespaces[namespace]
|
20
|
+
end
|
21
|
+
|
22
|
+
def refresh
|
23
|
+
read_namespaces
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to?(method)
|
27
|
+
namespaces.keys.include?(method) || super
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method, *args, &block)
|
31
|
+
if namespaces.keys.include? method.to_sym
|
32
|
+
return namespaces[method.to_sym]
|
33
|
+
end
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def configuration
|
41
|
+
@configuration ||= Configuration.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def redis_client
|
45
|
+
@redis_client ||= RedisClient.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def apply_configuration
|
49
|
+
register_namespaces
|
50
|
+
end
|
51
|
+
|
52
|
+
def register_namespaces
|
53
|
+
configuration.namespaces.each do |namespace_name|
|
54
|
+
redis_client.sadd(meta_key, namespace_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def namespaces
|
59
|
+
@namespaces ||= read_namespaces
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_namespaces
|
63
|
+
@namespaces = begin
|
64
|
+
redis_client.smembers(meta_key).reduce({}) do |namespaces, namespace_name|
|
65
|
+
namespaces[namespace_name.to_sym] = Namespace.new(namespace_key(namespace_name))
|
66
|
+
namespaces
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def namespace_key(namespace_name)
|
72
|
+
configuration.prefix + namespace_name.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Reconfig
|
2
|
+
class Configuration
|
3
|
+
attr_writer :namespaces, :meta_key, :prefix
|
4
|
+
|
5
|
+
def namespaces
|
6
|
+
@namespaces ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def meta_key
|
10
|
+
@meta_key ||= "#{prefix}_meta"
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix
|
14
|
+
@prefix ||= 'reconf:'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Reconfig
|
2
|
+
class Namespace
|
3
|
+
|
4
|
+
attr_accessor :meta_key, :options
|
5
|
+
|
6
|
+
def initialize(meta_key, opts={})
|
7
|
+
@meta_key = meta_key
|
8
|
+
@options = {
|
9
|
+
prefix: meta_key + ':'
|
10
|
+
}.merge opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@config ||= refresh_config
|
15
|
+
end
|
16
|
+
|
17
|
+
def refresh
|
18
|
+
refresh_keyspace
|
19
|
+
refresh_config
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
config[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
set(key, value)
|
28
|
+
config[key] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def key_space
|
34
|
+
@keyspace ||= refresh_keyspace
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh_keyspace
|
38
|
+
@keyspace = redis_client.zrange(meta_key, 0, -1, with_scores: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def refresh_config
|
42
|
+
@config = key_space.reduce({}) do |config_hash, (stored_key, value_type)|
|
43
|
+
namespaced_key = namespaced_key(stored_key)
|
44
|
+
config_hash[stored_key] = redis_client.fetch_by_type(namespaced_key, value_type)
|
45
|
+
config_hash
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def set(key, value)
|
50
|
+
stored_type = type_mapper.stored_type(value)
|
51
|
+
redis_client.set_by_type(namespaced_key(key), value)
|
52
|
+
redis_client.zadd(meta_key, stored_type, key)
|
53
|
+
end
|
54
|
+
|
55
|
+
def namespaced_key(key)
|
56
|
+
options[:prefix] + key.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def redis_client
|
60
|
+
@redis_client ||= RedisClient.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def type_mapper
|
64
|
+
@type_mapper ||= TypeMapper.new
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'redis'
|
2
|
+
module Reconfig
|
3
|
+
class RedisClient
|
4
|
+
|
5
|
+
def fetch_by_type(key, stored_type)
|
6
|
+
case stored_type
|
7
|
+
when type_mapper.string
|
8
|
+
connection.get(key)
|
9
|
+
when type_mapper.integer
|
10
|
+
connection.get(key).to_i
|
11
|
+
when type_mapper.float
|
12
|
+
connection.get(key).to_f
|
13
|
+
when type_mapper.hash
|
14
|
+
connection.hgetall(key)
|
15
|
+
when type_mapper.list
|
16
|
+
connection.lrange(key, 0, -1)
|
17
|
+
when type_mapper.set
|
18
|
+
connection.smembers(key)
|
19
|
+
else
|
20
|
+
raise UnknownTypeException.new("#{stored_type} is not a valid type.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Overwrites associated value
|
25
|
+
def set_by_type(key, value)
|
26
|
+
case type_mapper.stored_type(value)
|
27
|
+
when type_mapper.string, type_mapper.integer, type_mapper.float
|
28
|
+
connection.set(key, value)
|
29
|
+
when type_mapper.hash
|
30
|
+
connection.del key
|
31
|
+
connection.hmset(key, *value.to_a.flatten)
|
32
|
+
when type_mapper.list
|
33
|
+
connection.del key
|
34
|
+
connection.rpush key, *value
|
35
|
+
when type_mapper.set
|
36
|
+
connection.del key
|
37
|
+
connection.sadd key, *value
|
38
|
+
else
|
39
|
+
raise UnknownTypeException.new("Unable to store type #{value.class.name}.")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_to?(method)
|
44
|
+
connection.respond_to?(method) || super
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(method, *args, &block)
|
48
|
+
if connection.respond_to?(method)
|
49
|
+
return connection.send(method, *args, &block)
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def connection
|
57
|
+
connection = Redis.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def type_mapper
|
61
|
+
TypeMapper.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class UnknownTypeException < Exception; end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Reconfig
|
3
|
+
class TypeMapper
|
4
|
+
|
5
|
+
def stored_type(value)
|
6
|
+
case value
|
7
|
+
when String
|
8
|
+
string
|
9
|
+
when Integer
|
10
|
+
integer
|
11
|
+
when Float
|
12
|
+
float
|
13
|
+
when Hash
|
14
|
+
hash
|
15
|
+
when Array
|
16
|
+
list
|
17
|
+
when Set
|
18
|
+
set
|
19
|
+
else
|
20
|
+
raise UnknownTypeException.new("Cannot map #{value.class.name}.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Redis stores SCORES as floats, so types must be floats
|
25
|
+
|
26
|
+
def string
|
27
|
+
1.0
|
28
|
+
end
|
29
|
+
|
30
|
+
def integer
|
31
|
+
2.0
|
32
|
+
end
|
33
|
+
|
34
|
+
def float
|
35
|
+
3.0
|
36
|
+
end
|
37
|
+
|
38
|
+
def hash
|
39
|
+
4.0
|
40
|
+
end
|
41
|
+
|
42
|
+
def list
|
43
|
+
5.0
|
44
|
+
end
|
45
|
+
|
46
|
+
def set
|
47
|
+
6.0
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class UnknownTypeException < Exception; end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Reconfig
|
2
|
+
class Version
|
3
|
+
MAJOR = 0 unless defined? MAJOR
|
4
|
+
MINOR = 1 unless defined? MINOR
|
5
|
+
PATCH = 0 unless defined? PATCH
|
6
|
+
PRE = nil unless defined? PRE
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
def to_s
|
12
|
+
[MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/reconfig.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'reconfig/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.add_dependency 'redis', '= 3.0.5'
|
8
|
+
spec.authors = ['Chris Maddox']
|
9
|
+
spec.date = '2013-10-02'
|
10
|
+
spec.description = 'Redis configuration management.'
|
11
|
+
spec.email = 'chris@zenpayroll.com'
|
12
|
+
spec.files = %w(LICENSE.md README.md reconfig.gemspec)
|
13
|
+
spec.files += Dir.glob('lib/**/*.rb')
|
14
|
+
spec.files += Dir.glob('spec/**/*')
|
15
|
+
spec.homepage = 'http://rubygems.org/gems/reconfig'
|
16
|
+
spec.licenses = ['MIT']
|
17
|
+
spec.name = 'reconfig'
|
18
|
+
spec.summary = 'Redis configuration management'
|
19
|
+
spec.test_files += Dir.glob('spec/**/*')
|
20
|
+
spec.version = Reconfig::Version.to_s
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reconfig::Configuration do
|
4
|
+
subject do
|
5
|
+
Reconfig::Configuration.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#namespaces' do
|
9
|
+
it 'memoizes an empty array' do
|
10
|
+
subject.namespaces.should eq []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#meta_key' do
|
15
|
+
it 'memoizes to a default key' do
|
16
|
+
subject.meta_key.should eq 'reconf:_meta'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'takes into account a previously set prefix' do
|
20
|
+
subject.prefix = 'sooooo'
|
21
|
+
subject.meta_key.should eq 'sooooo_meta'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#prefix' do
|
26
|
+
it 'memoizes a default prefix' do
|
27
|
+
subject.prefix.should eq 'reconf:'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reconfig::Namespace do
|
4
|
+
let(:type_mapper) { Reconfig::TypeMapper.new }
|
5
|
+
let(:fake_redis) { Redis.new }
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Reconfig::Namespace.new('Meta:World:Peace')
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
it 'sets the meta key' do
|
13
|
+
subject.meta_key.should eq 'Meta:World:Peace'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'sets a default for the prefix' do
|
17
|
+
subject.options[:prefix].should eq 'Meta:World:Peace:'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#config' do
|
22
|
+
it 'defaults to an empty hash' do
|
23
|
+
subject.config.should eq Hash.new
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'stores a value' do
|
27
|
+
subject[:gin] = 'St. George'
|
28
|
+
fake_redis.get('Meta:World:Peace:gin').should eq 'St. George'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'stores a value\'s key and type in the meta key' do
|
32
|
+
subject[:gin] = 'St. George'
|
33
|
+
key_type = fake_redis.zrange('Meta:World:Peace', 0, -1, with_scores: true).first
|
34
|
+
key_type.first.should eq 'gin'
|
35
|
+
key_type.last.should eq type_mapper.string
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'fetchs configuration values from the meta key' do
|
39
|
+
fake_redis.zadd('Meta:World:Peace', type_mapper.integer, 'Crisco')
|
40
|
+
fake_redis.set('Meta:World:Peace:Crisco', 17)
|
41
|
+
subject['Crisco'].should eq 17
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'only loads the config once' do
|
45
|
+
subject.config.should eq Hash.new
|
46
|
+
fake_redis.zadd('Meta:World:Peace', type_mapper.integer, 'Crisco')
|
47
|
+
fake_redis.set('Meta:World:Peace:Crisco', 17)
|
48
|
+
subject['Crisco'].should be_nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#refresh' do
|
53
|
+
before(:each) do
|
54
|
+
fake_redis.zadd('Meta:World:Peace', type_mapper.integer, 'Crisco')
|
55
|
+
fake_redis.set('Meta:World:Peace:Crisco', 17)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'refreshes configuration values' do
|
59
|
+
subject['Crisco'].should be 17
|
60
|
+
fake_redis.set('Meta:World:Peace:Crisco', 42)
|
61
|
+
subject.refresh
|
62
|
+
subject['Crisco'].should be 42
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reconfig::RedisClient do
|
4
|
+
let(:type_mapper) { Reconfig::TypeMapper.new }
|
5
|
+
let(:connection) { double(:connection) }
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Reconfig::RedisClient.new
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:each) { subject.stub(:connection).and_return(connection) }
|
12
|
+
|
13
|
+
describe '#fetch_by_type' do
|
14
|
+
|
15
|
+
it 'gets a string value' do
|
16
|
+
allow(connection).to receive(:get).with('Gin').and_return('Tonic')
|
17
|
+
subject.fetch_by_type('Gin', type_mapper.string).should eq 'Tonic'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'gets an integer value' do
|
21
|
+
allow(connection).to receive(:get).with('Bubbles').and_return('7')
|
22
|
+
subject.fetch_by_type('Bubbles', type_mapper.integer).should be 7
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'gets a float value' do
|
26
|
+
allow(connection).to receive(:get).with('Stars').and_return('7.0')
|
27
|
+
subject.fetch_by_type('Stars', type_mapper.float).should eq 7.0
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'gets a hash value' do
|
31
|
+
allow(connection).to receive(:hgetall).with('Months').and_return({ january: 1, february: 2 })
|
32
|
+
subject.fetch_by_type('Months', type_mapper.hash).should eq({ january: 1, february: 2 })
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'gets a list value' do
|
36
|
+
allow(connection).to receive(:lrange).with('PescatarianSleepAid', 0, -1).and_return(['One Fish', 'Two Fish'])
|
37
|
+
subject.fetch_by_type('PescatarianSleepAid', type_mapper.list).should eq(['One Fish', 'Two Fish'])
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'gets a set value' do
|
41
|
+
set = Set.new(['A', 'B', 'AB', 'O'])
|
42
|
+
allow(connection).to receive(:smembers).with('BloodTypes').and_return(set)
|
43
|
+
subject.fetch_by_type('BloodTypes', type_mapper.set).should eq(set)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises an error for an unknown type' do
|
47
|
+
expect { subject.fetch_by_type('Betty', 'Boop') }.to raise_error(UnknownTypeException)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#set_by_type' do
|
52
|
+
|
53
|
+
context 'with a string' do
|
54
|
+
it 'sets the existing key' do
|
55
|
+
connection.should_not_receive(:del).with('Frank')
|
56
|
+
connection.should_receive(:set).with('Frank', 'Ocean')
|
57
|
+
subject.set_by_type('Frank', 'Ocean')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with an integer' do
|
62
|
+
it 'sets the existing key' do
|
63
|
+
connection.should_not_receive(:del).with('Bubbles')
|
64
|
+
connection.should_receive(:set).with('Bubbles', 7)
|
65
|
+
subject.set_by_type('Bubbles', 7)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with a float' do
|
70
|
+
it 'sets the existing key' do
|
71
|
+
connection.should_not_receive(:del).with('pi')
|
72
|
+
connection.should_receive(:set).with('pi', 3.14)
|
73
|
+
subject.set_by_type('pi', 3.14)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with an hash' do
|
78
|
+
it 'deletes the existing hash, sets the hash' do
|
79
|
+
hash = { javascript: 1, chris: 0 }
|
80
|
+
connection.should_receive(:del).with('life')
|
81
|
+
connection.should_receive(:hmset).with('life', *hash.to_a.flatten)
|
82
|
+
subject.set_by_type('life', hash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'with an array' do
|
87
|
+
it 'deletes the existing list, pushes all values' do
|
88
|
+
values = ['Jack', 'Jill']
|
89
|
+
connection.should_receive(:del).with('Chillins')
|
90
|
+
connection.should_receive(:rpush).with('Chillins', *values)
|
91
|
+
subject.set_by_type('Chillins', values)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with a set' do
|
96
|
+
it 'deletes the existing set, adds all values' do
|
97
|
+
set = Set.new(['A', 'B', 'AB', 'O'])
|
98
|
+
connection.should_receive(:del).with('BloodTypes')
|
99
|
+
connection.should_receive(:sadd).with('BloodTypes', *set)
|
100
|
+
subject.set_by_type('BloodTypes', set)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'respond_to?' do
|
106
|
+
let(:connection) { double(:connection, zadd: true, rpop: false, evalsha: 'whatever') }
|
107
|
+
|
108
|
+
it 'returns true for underlying redis commands' do
|
109
|
+
[:zadd, :rpop, :evalsha].each do |redis_command|
|
110
|
+
subject.respond_to?(redis_command).should be true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns false for arbitrary methods' do
|
115
|
+
subject.respond_to?(:bacon).should be false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'method_missing' do
|
120
|
+
let(:connection) { double(:connection, zadd: true) }
|
121
|
+
|
122
|
+
it 'passes methods to the underlying redis connection' do
|
123
|
+
connection.should_receive(:zadd).with('key', 'a', 'b', 'c')
|
124
|
+
subject.zadd('key', 'a', 'b', 'c')
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reconfig::TypeMapper do
|
4
|
+
subject do
|
5
|
+
Reconfig::TypeMapper.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#stored_type' do
|
9
|
+
it 'returns 1.0 for a string' do
|
10
|
+
subject.stored_type('Aquarius').should eq 1.0
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns 2.0 for a fixnum' do
|
14
|
+
subject.stored_type(4815162342).should eq 2.0
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns 2.0 for a bignum' do
|
18
|
+
subject.stored_type(983209180398120983012).should eq 2.0
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns 3.0 for a float' do
|
22
|
+
subject.stored_type(7.0).should eq 3.0
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns 4.0 for a hash' do
|
26
|
+
subject.stored_type(walrus: 'bubbles').should eq 4.0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns 5.0 for an array' do
|
30
|
+
subject.stored_type(['The Plague', 'The Fall', 'The Stranger']).should eq 5.0
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns 6.0 for a set' do
|
34
|
+
subject.stored_type(Set.new([1])).should eq 6.0
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'raises an UnknownTypeException for things it cannot handle.' do
|
38
|
+
expect { subject.stored_type BasicObject }.to raise_error UnknownTypeException
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reconfig do
|
4
|
+
let(:redis_client) { Redis.new }
|
5
|
+
|
6
|
+
after(:each) do
|
7
|
+
Reconfig.instance_variable_set(:@configuration, nil)
|
8
|
+
Reconfig.instance_variable_set(:@namespaces, nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.configure' do
|
12
|
+
it 'sets prefix and meta_key' do
|
13
|
+
Reconfig.configure do |config|
|
14
|
+
config.prefix = 'Re'
|
15
|
+
config.meta_key = 'Cola'
|
16
|
+
end
|
17
|
+
|
18
|
+
Reconfig.prefix.should eq 'Re'
|
19
|
+
Reconfig.meta_key.should eq 'Cola'
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'setting namespaces' do
|
23
|
+
before(:each) do
|
24
|
+
Reconfig.configure do |config|
|
25
|
+
config.namespaces = [:circle, :square]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'generates namespaces for the passed in options' do
|
30
|
+
Reconfig[:circle].meta_key.should eq 'reconf:circle'
|
31
|
+
Reconfig[:square].meta_key.should eq 'reconf:square'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'adds the keys to the meta_key' do
|
35
|
+
redis_client.smembers('reconf:_meta').should eq ['square', 'circle']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#method_missing' do
|
41
|
+
before(:each) do
|
42
|
+
Reconfig.configure do |config|
|
43
|
+
config.namespaces = [:circle, :square]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'delegates to stored namespaces by key' do
|
48
|
+
Reconfig.circle.should_not be_nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#refresh' do
|
53
|
+
before(:each) do
|
54
|
+
Reconfig.configure do |config|
|
55
|
+
config.namespaces = [:gold, :silver]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'adds new namespaces and deletes old ones' do
|
60
|
+
Reconfig.gold.should_not be_nil
|
61
|
+
redis_client.srem('reconf:_meta', 'gold')
|
62
|
+
Reconfig.refresh
|
63
|
+
expect{ Reconfig.gold }.to raise_error(NoMethodError)
|
64
|
+
Reconfig[:gold].should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reconfig
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Maddox
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.5
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.5
|
30
|
+
description: Redis configuration management.
|
31
|
+
email: chris@zenpayroll.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- LICENSE.md
|
37
|
+
- README.md
|
38
|
+
- reconfig.gemspec
|
39
|
+
- lib/reconfig/configuration.rb
|
40
|
+
- lib/reconfig/namespace.rb
|
41
|
+
- lib/reconfig/redis_client.rb
|
42
|
+
- lib/reconfig/type_mapper.rb
|
43
|
+
- lib/reconfig/version.rb
|
44
|
+
- lib/reconfig.rb
|
45
|
+
- spec/reconfig/configuration_spec.rb
|
46
|
+
- spec/reconfig/namespace_spec.rb
|
47
|
+
- spec/reconfig/redis_client_spec.rb
|
48
|
+
- spec/reconfig/type_mapper_spec.rb
|
49
|
+
- spec/reconfig_spec.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
homepage: http://rubygems.org/gems/reconfig
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.25
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Redis configuration management
|
76
|
+
test_files:
|
77
|
+
- spec/reconfig/configuration_spec.rb
|
78
|
+
- spec/reconfig/namespace_spec.rb
|
79
|
+
- spec/reconfig/redis_client_spec.rb
|
80
|
+
- spec/reconfig/type_mapper_spec.rb
|
81
|
+
- spec/reconfig_spec.rb
|
82
|
+
- spec/spec_helper.rb
|