togl 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 21a490d4260342d1ab7ddcce025f11fdad6464d7
4
- data.tar.gz: d6ca68b24b89b364b0ff534af5207ced033b145e
3
+ metadata.gz: f8d22d301e958363391bd6041c3bf737a70af7d4
4
+ data.tar.gz: 115609fb2028e80e2ebed5be9f4bca510fde64da
5
5
  SHA512:
6
- metadata.gz: a926954cd466dabb04212907cabf84a2411b74047fcdcca1a1d30d56a69364ddd3bf7822c3e262b8a39499972aca09c56d1fe3da5f7c2a95a006acc96794166d
7
- data.tar.gz: bb9014d73d3bded301bb02825fbd4a6b271016de9882e12a0724cb8fcbf1110d919de1e745ba88333ec0f0f4b224a6f1d04b1db4cf5f2baa5a220058aacc806a
6
+ metadata.gz: dfd5120f6b9b256c98a9e4cf697dd37ac0073c84222dd0db3b84e418556746cf95b3521409a71cdb61f3afe5314e2fecaa92f42ae39975c5f5c98533632273b5
7
+ data.tar.gz: 4562509334b6b52bd88b4c20289c3391d2b702854f1ff98bf1fee6302741c8f974fcb9daebd992926aa353cf1f82edfa0e7c3576884cba6d160411e99acb5386
@@ -0,0 +1,40 @@
1
+ language: ruby
2
+
3
+ script: bundle exec rake $TASK
4
+
5
+ sudo: false
6
+
7
+ rvm:
8
+ - 2.0
9
+ - 2.1
10
+ - 2.2
11
+ - jruby
12
+ - jruby-head
13
+ - rbx
14
+ - ruby-head
15
+
16
+ env:
17
+ - TASK=rspec
18
+ - TASK=mutant
19
+
20
+ matrix:
21
+ allow_failures:
22
+ - rvm: ruby-head
23
+ - rvm: jruby-head
24
+ - rvm: rbx
25
+ - env: TASK=mutant
26
+
27
+ # Only run mutant on 2.2
28
+ exclude:
29
+ - rvm: 2.0
30
+ env: TASK=mutant
31
+ - rvm: 2.1
32
+ env: TASK=mutant
33
+ - rvm: jruby
34
+ env: TASK=mutant
35
+ - rvm: jruby-head
36
+ env: TASK=mutant
37
+ - rvm: rbx
38
+ env: TASK=mutant
39
+ - rvm: ruby-head
40
+ env: TASK=mutant
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Togl
2
2
 
3
+ <img align="left" src="logo.jpg">
4
+
3
5
 
4
6
  ## Installation
5
7
 
@@ -20,6 +22,41 @@ Or install it directly:
20
22
 
21
23
  ## Usage
22
24
 
25
+ First have a configuration file where you add features as you introduce them
26
+
27
+ ``` ruby
28
+ Togl.configure do
29
+ use Togl::Adapter::RackSession.new
30
+
31
+ feature :recommendations
32
+ feature :threaded_comments
33
+ end
34
+ ```
35
+
36
+ This example uses only one adapter, one that monitors get parameters for special
37
+ `enable_features` or `disable_features` keys. To make it work, add the
38
+ `Togl::Rack::Middleware` to your Rack stack.
39
+
40
+ ``` ruby
41
+ use Togl::Rack::Middleware
42
+ ```
43
+
44
+ Now in your code you can check if a feature is on
45
+
46
+ ``` ruby
47
+ if Togl.on? :recommendations
48
+ # ... implement the feature ...
49
+ end
50
+ ```
51
+
52
+ Have a look at the `rails_example/` directory for how to use it with Rails, in
53
+ particular how to have a "features" model, so you can do this on the console:
54
+
55
+ ``` ruby
56
+ > Feature.enable! :recommendations
57
+ > Feature.disable! :recommendations
58
+ > Feature.reset! :recommendations
59
+ ```
23
60
 
24
61
  ## Contributing
25
62
 
@@ -8,15 +8,15 @@ module Togl
8
8
  end
9
9
 
10
10
  def self.configure(&block)
11
- Config::Builder.new(config, &block)
12
- config
11
+ @config ||= Config.new
12
+ @config.instance_eval(&block)
13
13
  end
14
14
 
15
- def on?(feature)
15
+ def self.on?(feature)
16
16
  config.on?(feature)
17
17
  end
18
18
 
19
- def off?(feature)
19
+ def self.off?(feature)
20
20
  config.on?(feature)
21
21
  end
22
22
  end
@@ -26,5 +26,4 @@ require "togl/util"
26
26
  require "togl/adapter"
27
27
  require "togl/adapter/rack_session"
28
28
  require "togl/config"
29
- require "togl/config/builder"
30
29
  require "togl/rack/middleware"
@@ -1,11 +1,9 @@
1
1
  module Togl
2
2
  class Adapter
3
- def self.all
4
- @all ||= {}
5
- end
3
+ attr_reader :name
6
4
 
7
- def self.register(name, callable)
8
- Adapter.all[name] = callable
5
+ def initialize(name)
6
+ @name = name.to_sym
9
7
  end
10
8
  end
11
9
  end
@@ -1,10 +1,13 @@
1
1
  module Togl
2
2
  class Adapter
3
3
  class RackSession < self
4
- register :rack_session, self
4
+ def initialize
5
+ super(:rack_session)
6
+ end
5
7
 
6
- def self.call(name)
7
- Thread.current[:togl_session_features][name.to_s]
8
+ def call(name)
9
+ features = Thread.current[:togl_session_features]
10
+ features && features[name.to_s]
8
11
  end
9
12
  end
10
13
  end
@@ -0,0 +1,37 @@
1
+ module Togl
2
+ class Adapter
3
+ class Redis < self
4
+ MAPPING = { "true" => true,
5
+ "false" => false }
6
+
7
+ def initialize
8
+ super(:redis)
9
+ end
10
+
11
+ def call(name)
12
+ MAPPING[current_redis.get(key(name))]
13
+ end
14
+
15
+ def enable!(name)
16
+ current_redis.set(key(name), true)
17
+ end
18
+
19
+ def disable!(name)
20
+ current_redis.set(key(name), false)
21
+ end
22
+
23
+ def reset!(name)
24
+ current_redis.del(key(name))
25
+ end
26
+
27
+ def key(name)
28
+ "togl.feature:#{name}"
29
+ end
30
+
31
+ attr_writer :current_redis
32
+ def current_redis
33
+ @current_redis || ::Redis.current
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,14 +1,18 @@
1
1
  module Togl
2
2
  class Config
3
- include Attribs.new(:features, :adapters, :default_adapters)
4
- attr_writer :default_adapters
3
+ attr_accessor :features, :adapters, :default_adapters
5
4
 
6
5
  def initialize(args = {}, &block)
7
- super({features: [], adapters: Adapter.all, default_adapters: []}.merge(args))
8
- Builder.new(self, &block) if block_given?
6
+ @features = args.fetch(:features, [])
7
+ @adapters = args.fetch(:adapters, {})
8
+ @default_adapters = args[:default_adapters]
9
+
10
+ instance_eval(&block) if block_given?
9
11
  end
10
12
 
11
- def add_feature(name, opts = {})
13
+ # Commands
14
+
15
+ def feature(name, opts = {})
12
16
  name = name.to_sym
13
17
  opts = {
14
18
  name: name,
@@ -19,12 +23,17 @@ module Togl
19
23
  self
20
24
  end
21
25
 
22
- def add_adapter(name, callable)
23
- name = name.to_sym
24
- adapters.merge!(name => callable)
26
+ def use(adapter)
27
+ adapters[adapter.name] = adapter
25
28
  self
26
29
  end
27
30
 
31
+ # Queries
32
+
33
+ def default_adapters
34
+ @default_adapters || adapters.keys
35
+ end
36
+
28
37
  def fetch(name)
29
38
  name = name.to_sym
30
39
  features.detect do |feature|
@@ -44,8 +53,5 @@ module Togl
44
53
  !on?(name)
45
54
  end
46
55
 
47
- def rack_middleware
48
- Rack::Middleware
49
- end
50
56
  end
51
57
  end
@@ -1,6 +1,8 @@
1
1
  module Togl
2
2
  module Rack
3
3
  class Middleware
4
+ SESSION_KEY = 'togl.features'
5
+
4
6
  def initialize(app)
5
7
  @app = app
6
8
  end
@@ -14,17 +16,17 @@ module Togl
14
16
  def detect_feature_params(env)
15
17
  params = ::Rack::Utils.parse_query(env["QUERY_STRING"])
16
18
  session = env["rack.session"]
17
- session_features = session["features"] || {}
19
+ session[SESSION_KEY] ||= {}
18
20
  { ["enable_feature", "enable_features"] => true,
19
21
  ["disable_feature", "disable_features"] => false,
20
22
  ["reset_feature", "reset_features"] => nil }.each do |keys, flag|
21
23
  keys.each do |key|
22
24
  feature_names(params, key).each do |feature|
23
- session_features[feature] = flag
25
+ session[SESSION_KEY][feature] = flag
24
26
  end
25
27
  end
26
28
  end
27
- session_features
29
+ session[SESSION_KEY]
28
30
  end
29
31
 
30
32
  def feature_names(params, key)
@@ -1,3 +1,3 @@
1
1
  module Togl
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
Binary file
@@ -0,0 +1,9 @@
1
+ class CreateFeatures < ActiveRecord::Migration
2
+ def change
3
+ create_table :features do |t|
4
+ t.string :name
5
+ t.boolean :enabled
6
+ t.timestamps null: false
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ # Add this to your application.rb
2
+
3
+ config.middleware.use Togl::Rack::Middleware
@@ -0,0 +1,34 @@
1
+ class Feature < ActiveRecord::Base
2
+ after_save :expire_cache
3
+
4
+ def expire_cache
5
+ Rails.cache.delete("feature_#{name}")
6
+ end
7
+
8
+ def self.enable!(name)
9
+ f = Feature.find_by(name: name)
10
+ if f
11
+ f.update_attributes!(enabled: true)
12
+ else
13
+ Feature.create(name: name, enabled: true)
14
+ end
15
+ end
16
+
17
+ def self.disable!(name)
18
+ f = Feature.find_by(name: name)
19
+ if f
20
+ f.update_attributes!(enabled: false)
21
+ else
22
+ Feature.create(name: name, enabled: false)
23
+ end
24
+ end
25
+
26
+ def self.reset!(name)
27
+ f = Feature.find_by(name: name)
28
+ if f
29
+ f.update_attributes!(enabled: false)
30
+ else
31
+ Feature.create(name: name, enabled: nil)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ # initializer
2
+
3
+ require 'togl'
4
+
5
+ Togl.configure do
6
+ use Togl::Adapter::RackSession.new
7
+ use Class.new(Togl::Adapter) {
8
+ def call(name)
9
+ Rails.cache.fetch("feature_#{name}") do
10
+ Feature.where(name: name).pluck(:enabled).first
11
+ end
12
+ end
13
+ }.new(:db)
14
+
15
+ feature :raffle_on
16
+ end
@@ -1,30 +1,33 @@
1
- RSpec.describe "high level usage examples" do
2
- let(:togl) do
3
- Togl::Config.new do
4
- adapters :rack_session
1
+ RSpec.describe "high level usage" do
2
+ context "using the rack session" do
3
+ let(:togl) do
4
+ Togl::Config.new do
5
+ use Togl::Adapter::RackSession.new
5
6
 
6
- feature :hero
7
- feature :frontend
7
+ feature :hero
8
+ feature :frontend
9
+ end
8
10
  end
9
- end
10
11
 
11
- specify do
12
- called = false
12
+ specify do
13
+ called = false
13
14
 
14
- app = ->(env) do
15
- called = true
15
+ app = ->(env) do
16
+ called = true
16
17
 
17
- expect(togl.on? :hero).to be true
18
- expect(togl.on? :frontend).to be false
19
- end
18
+ expect(togl.on? :hero).to be true
19
+ expect(togl.on? :frontend).to be false
20
+ end
20
21
 
21
- app = togl.rack_middleware.new(app)
22
- env = {
23
- "rack.session" => {},
24
- "QUERY_STRING" => "enable_feature=hero"
25
- }
26
- app.call(env)
22
+ app = Togl::Rack::Middleware.new(app)
23
+ env = {
24
+ "rack.session" => {},
25
+ "QUERY_STRING" => "enable_feature=hero"
26
+ }
27
+ app.call(env)
28
+ app.call("rack.session" => env["rack.session"])
27
29
 
28
- expect(called).to be true
30
+ expect(called).to be true
31
+ end
29
32
  end
30
33
  end
@@ -1,9 +1,7 @@
1
1
  RSpec.describe Togl::Config do
2
2
  let(:config) do
3
3
  described_class.new
4
- .add_feature("foo")
5
- .add_adapter("foo", ->(n){ :result })
6
- .add_adapter("bar", ->(n){ :rasilt })
4
+ .feature("foo")
7
5
  end
8
6
 
9
7
  describe "#initialize" do
@@ -23,7 +21,7 @@ RSpec.describe Togl::Config do
23
21
  it "should have defaults for its attributes" do
24
22
  config = described_class.new
25
23
  expect(config.features).to eql []
26
- expect(config.adapters).to eql Togl::Adapter.all
24
+ expect(config.adapters).to eql({})
27
25
  expect(config.default_adapters).to eql []
28
26
  end
29
27
  end
@@ -34,24 +32,24 @@ RSpec.describe Togl::Config do
34
32
  end
35
33
 
36
34
  it "takes options" do
37
- config.add_feature(:wammie, adapters: [:redis, :rack])
35
+ config.feature(:wammie, adapters: [:redis, :rack])
38
36
  expect(config.fetch(:wammie))
39
37
  .to eql Togl::Feature.new(name: :wammie, config: config, adapters: [:redis, :rack])
40
38
  end
41
39
 
42
- it "should use default adapters if none given" do
43
- config.default_adapters.push(:rack)
44
- config.add_feature(:lisa)
45
- expect(config.fetch(:lisa)).to eql Togl::Feature.new(name: :lisa, config: config, adapters: [:rack])
40
+ it "should use all adapters as default if not explicitly configured given" do
41
+ config.use Togl::Adapter::RackSession.new
42
+ config.feature(:lisa)
43
+ expect(config.fetch(:lisa)).to eql Togl::Feature.new(name: :lisa, config: config, adapters: [:rack_session])
46
44
  end
47
45
  end
48
46
 
49
47
  describe '#fetch' do
50
48
  let(:config) do
51
49
  described_class.new
52
- .add_feature(:foo)
53
- .add_feature("bar")
54
- .add_feature(:baz)
50
+ .feature(:foo)
51
+ .feature("bar")
52
+ .feature(:baz)
55
53
  end
56
54
 
57
55
  it "should find a feature object by name" do
@@ -62,32 +60,11 @@ RSpec.describe Togl::Config do
62
60
  describe "#on?" do
63
61
  fake(:feature, name: :disrupt)
64
62
  it "should delegate to the feature" do
65
- config = self.config.append_to(:features, feature)
63
+ config.features << feature
66
64
 
67
65
  config.on?("disrupt")
68
66
  expect(feature).to have_received.on?
69
67
  end
70
68
  end
71
69
 
72
- describe "#add_adapter" do
73
-
74
- it "should store the adapter" do
75
- expect(config.adapters[:foo].call(:bar)).to equal :result
76
- end
77
- end
78
-
79
- describe "#fetch_adapter" do
80
- it "should return the adapter by name" do
81
- expect(config.fetch_adapter("bar").call(1)).to equal :rasilt
82
- end
83
- end
84
-
85
- describe "rack_middleware" do
86
- it "should return a rack middleware builder" do
87
- Togl::Config::Builder.new(config) do
88
- adapters :rack_session
89
- end
90
- expect(config.rack_middleware.new(->(env){})).to be_a Togl::Rack::Middleware
91
- end
92
- end
93
70
  end
@@ -12,52 +12,4 @@ RSpec.describe Togl::Feature do
12
12
  let(:config) { Togl::Config.new }
13
13
  let(:default) { :off }
14
14
  let(:adapters) { [] }
15
-
16
- describe "#on?" do
17
- it "should be disabled by default" do
18
- expect(feature.on?).to be false
19
- end
20
-
21
- context "when on by default" do
22
- let(:adapters) { [:s1, :s2] }
23
- let(:default) { :on }
24
-
25
- it "should be on if all strategues return nil" do
26
- config
27
- .add_adapter(:s1, ->(name){ nil })
28
- .add_adapter(:s2, ->(name){ nil })
29
- expect(feature.on?).to be true
30
- end
31
- end
32
-
33
- context "with adapters" do
34
- let(:adapters) { [:s1, :s2] }
35
-
36
- it "should be on if the adapter returns true - skipping nils" do
37
- config
38
- .add_adapter(:s1, ->(name){ nil })
39
- .add_adapter(:s2, ->(name){ name == :eric })
40
- expect(feature.on?).to be true
41
- end
42
-
43
- it "should be off if the adapter returns false - skipping nils" do
44
- config
45
- .add_adapter(:s1, ->(name){ nil })
46
- .add_adapter(:s2, ->(name){ name != :eric })
47
- expect(feature.on?).to be false
48
- end
49
-
50
- it "should be on if the adapter returns true" do
51
- config
52
- .add_adapter(:s1, ->(name){ name == :eric })
53
- expect(feature.on?).to be true
54
- end
55
-
56
- it "should be off if the adapter returns false" do
57
- config
58
- .add_adapter(:s1, ->(name){ name != :eric })
59
- expect(feature.on?).to be false
60
- end
61
- end
62
- end
63
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: togl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arne Brasseur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-21 00:00:00.000000000 Z
11
+ date: 2016-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -104,6 +104,7 @@ extra_rdoc_files:
104
104
  files:
105
105
  - ".gitignore"
106
106
  - ".rspec"
107
+ - ".travis.yml"
107
108
  - Gemfile
108
109
  - LICENSE.txt
109
110
  - README.md
@@ -111,15 +112,19 @@ files:
111
112
  - lib/togl.rb
112
113
  - lib/togl/adapter.rb
113
114
  - lib/togl/adapter/rack_session.rb
115
+ - lib/togl/adapter/redis.rb
114
116
  - lib/togl/config.rb
115
- - lib/togl/config/builder.rb
116
117
  - lib/togl/feature.rb
117
118
  - lib/togl/rack/middleware.rb
118
119
  - lib/togl/util.rb
119
120
  - lib/togl/version.rb
121
+ - logo.jpg
122
+ - rails_example/20161029135115_create_features.rb
123
+ - rails_example/config.rb
124
+ - rails_example/feature.rb
125
+ - rails_example/togl_initializer.rb
120
126
  - spec/integration_spec.rb
121
127
  - spec/spec_helper.rb
122
- - spec/togl/config/builder_spec.rb
123
128
  - spec/togl/config_spec.rb
124
129
  - spec/togl/feature_spec.rb
125
130
  - spec/togl_spec.rb
@@ -144,14 +149,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
149
  version: '0'
145
150
  requirements: []
146
151
  rubyforge_project:
147
- rubygems_version: 2.2.2
152
+ rubygems_version: 2.4.5.1
148
153
  signing_key:
149
154
  specification_version: 4
150
155
  summary: Perfect Features Toggles
151
156
  test_files:
152
157
  - spec/integration_spec.rb
153
158
  - spec/spec_helper.rb
154
- - spec/togl/config/builder_spec.rb
155
159
  - spec/togl/config_spec.rb
156
160
  - spec/togl/feature_spec.rb
157
161
  - spec/togl_spec.rb
@@ -1,22 +0,0 @@
1
- module Togl
2
- class Config
3
- class Builder
4
- def initialize(config, &block)
5
- @config = config
6
- instance_eval(&block)
7
- end
8
-
9
- def feature(name, opts = {})
10
- @config.add_feature(name, opts)
11
- end
12
-
13
- def adapter(name, callable)
14
- @config.add_adapter(name, callable)
15
- end
16
-
17
- def adapters(*adapters)
18
- @config.default_adapters = adapters
19
- end
20
- end
21
- end
22
- end
@@ -1,23 +0,0 @@
1
- RSpec.describe Togl::Config::Builder do
2
- let(:config) { Togl::Config.new }
3
- let!(:builder) {
4
- Togl::Config::Builder.new(config) do
5
- feature :foo, default: :on
6
- feature :bar
7
- adapters :rack_session
8
- end
9
- }
10
-
11
- describe "#initialize" do
12
- it "takes a Togl config and a configuration block" do
13
- expect(config.fetch(:foo)).to eql Togl::Feature.new(name: :foo, default: :on, config: config)
14
- end
15
- end
16
-
17
- describe "#feature" do
18
- it "works with only a name" do
19
- expect(config.fetch(:bar)).to eql Togl::Feature.new(name: :bar, config: config)
20
-
21
- end
22
- end
23
- end