togl 0.0.1 → 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.
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