flipper-redis 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,9 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
+ gem 'redis-namespace', :require => false
4
5
  gem 'rake'
5
6
  gem 'rspec'
6
- gem 'redis'
7
7
 
8
8
  group(:guard) do
9
9
  gem 'guard'
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Flipper Redis
2
2
 
3
- A redis adapter for [Flipper](https://github.com/jnunemaker/flipper), the feature flipping gems.
3
+ A [Redis](https://github.com/redis/redis-rb) adapter for [Flipper](https://github.com/jnunemaker/flipper).
4
4
 
5
5
  ## Installation
6
6
 
@@ -26,6 +26,72 @@ flipper = Flipper.new(adapter)
26
26
  # profit...
27
27
  ```
28
28
 
29
+ ## Internals
30
+
31
+ Each feature is stored in a redis hash, which means getting a feature is single query.
32
+
33
+ ```ruby
34
+ require 'flipper/adapters/redis'
35
+ require 'redis/namespace'
36
+
37
+ client = Redis.new
38
+ namespaced_client = Redis::Namespace.new(:flipper, :redis => client)
39
+ adapter = Flipper::Adapters::Redis.new(namespaced_client)
40
+ flipper = Flipper.new(adapter)
41
+
42
+ # Register a few groups.
43
+ Flipper.register(:admins) { |thing| thing.admin? }
44
+ Flipper.register(:early_access) { |thing| thing.early_access? }
45
+
46
+ # Create a user class that has flipper_id instance method.
47
+ User = Struct.new(:flipper_id)
48
+
49
+ flipper[:stats].enable
50
+ flipper[:stats].enable flipper.group(:admins)
51
+ flipper[:stats].enable flipper.group(:early_access)
52
+ flipper[:stats].enable User.new('25')
53
+ flipper[:stats].enable User.new('90')
54
+ flipper[:stats].enable User.new('180')
55
+ flipper[:stats].enable flipper.random(15)
56
+ flipper[:stats].enable flipper.actors(45)
57
+
58
+ flipper[:search].enable
59
+
60
+ print 'all keys: '
61
+ pp namespaced_client.keys
62
+ # all keys: ["stats", "flipper_features", "search"]
63
+
64
+ print "known flipper features: "
65
+ pp namespaced_client.smembers("flipper_features")
66
+ # known flipper features: ["stats", "search"]
67
+
68
+ puts 'stats keys'
69
+ pp namespaced_client.hgetall('stats')
70
+ # stats keys
71
+ # {"boolean"=>"true",
72
+ # "groups/admins"=>"1",
73
+ # "actors/25"=>"1",
74
+ # "percentage_of_random"=>"15",
75
+ # "percentage_of_actors"=>"45",
76
+ # "groups/early_access"=>"1",
77
+ # "actors/90"=>"1",
78
+ # "actors/180"=>"1"}
79
+
80
+ puts 'search keys'
81
+ pp namespaced_client.hgetall('search')
82
+ # search keys
83
+ # {"boolean"=>"true"}
84
+
85
+ puts 'flipper get of feature'
86
+ pp adapter.get(flipper[:stats])
87
+ # flipper get of feature
88
+ # {:boolean=>"true",
89
+ # :groups=>#<Set: {"admins", "early_access"}>,
90
+ # :actors=>#<Set: {"25", "90", "180"}>,
91
+ # :percentage_of_actors=>"45",
92
+ # :percentage_of_random=>"15"}
93
+ ```
94
+
29
95
  ## Contributing
30
96
 
31
97
  1. Fork it
data/examples/basic.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'pathname'
2
+ require 'logger'
3
+
4
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
5
+ lib_path = root_path.join('lib')
6
+ $:.unshift(lib_path)
7
+
8
+ require 'flipper/adapters/redis'
9
+ client = Redis.new
10
+ adapter = Flipper::Adapters::Redis.new(client)
11
+ flipper = Flipper.new(adapter)
12
+
13
+ flipper[:stats].enable
14
+
15
+ if flipper[:stats].enabled?
16
+ puts "Enabled!"
17
+ else
18
+ puts "Disabled!"
19
+ end
20
+
21
+ flipper[:stats].disable
22
+
23
+ if flipper[:stats].enabled?
24
+ puts "Enabled!"
25
+ else
26
+ puts "Disabled!"
27
+ end
@@ -0,0 +1,71 @@
1
+ require 'pp'
2
+ require 'pathname'
3
+ require 'logger'
4
+
5
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
6
+ lib_path = root_path.join('lib')
7
+ $:.unshift(lib_path)
8
+
9
+ require 'flipper/adapters/redis'
10
+ require 'redis/namespace'
11
+
12
+ client = Redis.new
13
+ namespaced_client = Redis::Namespace.new(:flipper, :redis => client)
14
+ adapter = Flipper::Adapters::Redis.new(namespaced_client)
15
+ flipper = Flipper.new(adapter)
16
+
17
+ # Register a few groups.
18
+ Flipper.register(:admins) { |thing| thing.admin? }
19
+ Flipper.register(:early_access) { |thing| thing.early_access? }
20
+
21
+ # Create a user class that has flipper_id instance method.
22
+ User = Struct.new(:flipper_id)
23
+
24
+ flipper[:stats].enable
25
+ flipper[:stats].enable flipper.group(:admins)
26
+ flipper[:stats].enable flipper.group(:early_access)
27
+ flipper[:stats].enable User.new('25')
28
+ flipper[:stats].enable User.new('90')
29
+ flipper[:stats].enable User.new('180')
30
+ flipper[:stats].enable flipper.random(15)
31
+ flipper[:stats].enable flipper.actors(45)
32
+
33
+ flipper[:search].enable
34
+
35
+ print 'all keys: '
36
+ pp namespaced_client.keys
37
+ # all keys: ["stats", "flipper_features", "search"]
38
+ puts
39
+
40
+ print "known flipper features: "
41
+ pp namespaced_client.smembers("flipper_features")
42
+ # known flipper features: ["stats", "search"]
43
+ puts
44
+
45
+ puts 'stats keys'
46
+ pp namespaced_client.hgetall('stats')
47
+ # stats keys
48
+ # {"boolean"=>"true",
49
+ # "groups/admins"=>"1",
50
+ # "actors/25"=>"1",
51
+ # "percentage_of_random"=>"15",
52
+ # "percentage_of_actors"=>"45",
53
+ # "groups/early_access"=>"1",
54
+ # "actors/90"=>"1",
55
+ # "actors/180"=>"1"}
56
+ puts
57
+
58
+ puts 'search keys'
59
+ pp namespaced_client.hgetall('search')
60
+ # search keys
61
+ # {"boolean"=>"true"}
62
+ puts
63
+
64
+ puts 'flipper get of feature'
65
+ pp adapter.get(flipper[:stats])
66
+ # flipper get of feature
67
+ # {:boolean=>"true",
68
+ # :groups=>#<Set: {"admins", "early_access"}>,
69
+ # :actors=>#<Set: {"25", "90", "180"}>,
70
+ # :percentage_of_actors=>"45",
71
+ # :percentage_of_random=>"15"}
@@ -2,17 +2,19 @@
2
2
  require File.expand_path('../lib/flipper/adapters/redis/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
+ gem.name = "flipper-redis"
6
+ gem.version = Flipper::Adapters::Redis::VERSION
5
7
  gem.authors = ["John Nunemaker"]
6
8
  gem.email = ["nunemaker@gmail.com"]
7
9
  gem.description = %q{Redis adapter for Flipper}
8
10
  gem.summary = %q{Redis adapter for Flipper}
9
11
  gem.homepage = "http://jnunemaker.github.com/flipper-redis"
10
-
11
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
- gem.files = `git ls-files`.split("\n")
13
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
- gem.name = "flipper-redis"
15
12
  gem.require_paths = ["lib"]
16
- gem.version = Flipper::Adapters::Redis::VERSION
17
- gem.add_dependency 'flipper', '~> 0.4'
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+
18
+ gem.add_dependency 'flipper', '~> 0.5.0'
19
+ gem.add_dependency 'redis', '~> 3.0.0'
18
20
  end
data/lib/flipper-redis.rb CHANGED
@@ -1,2 +1 @@
1
- require 'flipper'
2
1
  require 'flipper/adapters/redis'
@@ -1,35 +1,126 @@
1
1
  require 'set'
2
2
  require 'redis'
3
+ require 'flipper'
3
4
 
4
5
  module Flipper
5
6
  module Adapters
6
7
  class Redis
8
+ include Flipper::Adapter
9
+
10
+ # Private: The key that stores the set of known features.
11
+ FeaturesKey = :flipper_features
12
+
13
+ # Public: The name of the adapter.
14
+ attr_reader :name
15
+
16
+ # Public: Initializes a Redis flipper adapter.
17
+ #
18
+ # client - The Redis client to use. Feel free to namespace it.
7
19
  def initialize(client)
8
20
  @client = client
21
+ @name = :redis
22
+ end
23
+
24
+ # Public: Gets the values for all gates for a given feature.
25
+ #
26
+ # Returns a Hash of Flipper::Gate#key => value.
27
+ def get(feature)
28
+ result = {}
29
+ doc = doc_for(feature)
30
+ fields = doc.keys
31
+
32
+ feature.gates.each do |gate|
33
+ result[gate.key] = case gate.data_type
34
+ when :boolean, :integer
35
+ doc[gate.key.to_s]
36
+ when :set
37
+ fields_to_gate_value fields, gate
38
+ else
39
+ unsupported_data_type gate.data_type
40
+ end
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ # Public: Enables a gate for a given thing.
47
+ #
48
+ # feature - The Flipper::Feature for the gate.
49
+ # gate - The Flipper::Gate to disable.
50
+ # thing - The Flipper::Type being disabled for the gate.
51
+ #
52
+ # Returns true.
53
+ def enable(feature, gate, thing)
54
+ case gate.data_type
55
+ when :boolean, :integer
56
+ @client.hset feature.key, gate.key, thing.value.to_s
57
+ when :set
58
+ @client.hset feature.key, to_field(gate, thing), 1
59
+ else
60
+ unsupported_data_type gate.data_type
61
+ end
62
+
63
+ true
64
+ end
65
+
66
+ # Public: Disables a gate for a given thing.
67
+ #
68
+ # feature - The Flipper::Feature for the gate.
69
+ # gate - The Flipper::Gate to disable.
70
+ # thing - The Flipper::Type being disabled for the gate.
71
+ #
72
+ # Returns true.
73
+ def disable(feature, gate, thing)
74
+ case gate.data_type
75
+ when :boolean
76
+ @client.del feature.key
77
+ when :integer
78
+ @client.hset feature.key, gate.key, thing.value.to_s
79
+ when :set
80
+ @client.hdel feature.key, to_field(gate, thing)
81
+ else
82
+ unsupported_data_type gate.data_type
83
+ end
84
+
85
+ true
9
86
  end
10
87
 
11
- def read(key)
12
- @client.get key.to_s
88
+ # Public: Adds a feature to the set of known features.
89
+ def add(feature)
90
+ @client.sadd FeaturesKey, feature.name
91
+ true
13
92
  end
14
93
 
15
- def write(key, value)
16
- @client.set key.to_s, value.to_s
94
+ # Public: The set of known features.
95
+ def features
96
+ @client.smembers(FeaturesKey).to_set
17
97
  end
18
98
 
19
- def delete(key)
20
- @client.del key.to_s
99
+ # Private: Gets a hash of fields => values for the given feature.
100
+ #
101
+ # Returns a Hash of fields => values.
102
+ def doc_for(feature)
103
+ @client.hgetall(feature.key)
21
104
  end
22
105
 
23
- def set_add(key, value)
24
- @client.sadd(key.to_s, value.to_s)
106
+ # Private: Converts gate and thing to hash key.
107
+ def to_field(gate, thing)
108
+ "#{gate.key}/#{thing.value}"
25
109
  end
26
110
 
27
- def set_delete(key, value)
28
- @client.srem(key.to_s, value.to_s)
111
+ # Private: Returns a set of values given an array of fields and a gate.
112
+ #
113
+ # Returns a Set of the values enabled for the gate.
114
+ def fields_to_gate_value(fields, gate)
115
+ regex = /^#{Regexp.escape(gate.key)}\//
116
+ keys = fields.grep(regex)
117
+ values = keys.map { |key| key.split('/', 2).last }
118
+ values.to_set
29
119
  end
30
120
 
31
- def set_members(key)
32
- @client.smembers(key.to_s).to_set
121
+ # Private
122
+ def unsupported_data_type(data_type)
123
+ raise "#{data_type} is not supported by this adapter"
33
124
  end
34
125
  end
35
126
  end
@@ -1,7 +1,7 @@
1
1
  module Flipper
2
2
  module Adapters
3
3
  class Redis
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
6
6
  end
7
7
  end
@@ -5,32 +5,11 @@ require 'flipper/spec/shared_adapter_specs'
5
5
  describe Flipper::Adapters::Redis do
6
6
  let(:client) { Redis.new }
7
7
 
8
- subject { Flipper::Adapters::Redis.new(client) }
8
+ subject { described_class.new(client) }
9
9
 
10
10
  before do
11
11
  client.flushdb
12
12
  end
13
13
 
14
- def read_key(key)
15
- client.get key.to_s
16
- rescue RuntimeError => e
17
- if e.message =~ /wrong kind of value/
18
- client.smembers(key.to_s).to_set
19
- else
20
- raise
21
- end
22
- end
23
-
24
- def write_key(key, value)
25
- case value
26
- when Array, Set
27
- value.each do |member|
28
- client.sadd key.to_s, member.to_s
29
- end
30
- else
31
- client.set key.to_s, value.to_s
32
- end
33
- end
34
-
35
14
  it_should_behave_like 'a flipper adapter'
36
15
  end
data/spec/helper.rb CHANGED
@@ -22,4 +22,5 @@ RSpec.configure do |config|
22
22
  config.alias_example_to :fit, :focused => true
23
23
  config.alias_example_to :xit, :pending => true
24
24
  config.run_all_when_everything_filtered = true
25
+ config.fail_fast = true
25
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-06 00:00:00.000000000 Z
12
+ date: 2013-02-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: flipper
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0.4'
21
+ version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,23 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0.4'
29
+ version: 0.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: redis
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.0.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.0.0
30
46
  description: Redis adapter for Flipper
31
47
  email:
32
48
  - nunemaker@gmail.com
@@ -41,6 +57,8 @@ files:
41
57
  - LICENSE
42
58
  - README.md
43
59
  - Rakefile
60
+ - examples/basic.rb
61
+ - examples/internals.rb
44
62
  - flipper-redis.gemspec
45
63
  - lib/flipper-redis.rb
46
64
  - lib/flipper/adapters/redis.rb
@@ -61,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
79
  version: '0'
62
80
  segments:
63
81
  - 0
64
- hash: 1698098931398913105
82
+ hash: -1629782830808389875
65
83
  required_rubygems_version: !ruby/object:Gem::Requirement
66
84
  none: false
67
85
  requirements:
@@ -70,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
88
  version: '0'
71
89
  segments:
72
90
  - 0
73
- hash: 1698098931398913105
91
+ hash: -1629782830808389875
74
92
  requirements: []
75
93
  rubyforge_project:
76
94
  rubygems_version: 1.8.23