flippeur 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in flippeur.gemspec
4
2
  gemspec
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Flippeur
2
2
 
3
- TODO: Write a gem description
3
+ Flippeur is a simple feature flipper. Features are setup with a simple Ruby DSL where you specify whether a feature is available to a user.
4
+
5
+ If you're using Rails you can pop your feature definition file in an initializer.
6
+
7
+ Features are not stored in a database and there's no web UI for toggling them.
4
8
 
5
9
  ## Installation
6
10
 
@@ -18,12 +22,103 @@ Or install it yourself as:
18
22
 
19
23
  ## Usage
20
24
 
21
- TODO: Write usage instructions here
25
+ The basic idea is that a feature is, or isn't, available to a user of your webapp. So the logic determining whether or not a feature is available acts on a user.
26
+
27
+ In the view and the controller layers, the user is obtained automatically by calling the controller's `current_person` method (currently and regrettably hardcoded). In the model layer you need to pass an appropriate user instance explicitly.
28
+
29
+ (This is suboptimal and I'm exploring alternatives.)
30
+
31
+ First, setup your features. With Rails this would go in `config/initializers/flippeur.rb`.
32
+
33
+ ```ruby
34
+ Flippeur.setup do
35
+
36
+ feature :something_fancy do |user|
37
+ user.is_fancy?
38
+ end
39
+
40
+ feature :something_else do |user|
41
+ user.name =~ /bob/i
42
+ end
43
+
44
+ end
45
+ ```
46
+
47
+ Give each feature a name and a block. The block should return a truthy value if the feature is available to the user, and a falsey value otherwise.
48
+
49
+ N.B. to return early from the block, use `next retvalue` instead of `return retvalue`. For example:
50
+
51
+ ```ruby
52
+ feature :add_widget do |user|
53
+ next false unless user.company.pays_lots_of_money?
54
+ user.admin? || user.my_friend?
55
+ end
56
+ ```
57
+
58
+ Second, use the helpers to determine whether or not features are available to your current user.
59
+
60
+ ### View
61
+
62
+ Use the `feature?(name)` helper with or without a block.
63
+
64
+ Without a block it acts as a simple conditional. For example:
65
+
66
+ ```erb
67
+ <% if feature? :something_fancy %>
68
+ <p>Here is something fancy.</p>
69
+ <% else %>
70
+ <p>You could have something fancy if you upgraded...</p>
71
+ <% end %>
72
+ ```
73
+
74
+ If you pass a block it will only be called if the feature is available. For example:
75
+
76
+ ```erb
77
+ <% feature? :something_fancy do %>
78
+ <p>Here is something fancy.</p>
79
+ <% end %>
80
+ ```
81
+
82
+ ### Controller
83
+
84
+ Exactly the same as the view. For example:
85
+
86
+ ```ruby
87
+ def create
88
+ if feature? :something_fancy
89
+ # the usual stuff
90
+ else
91
+ redirect_to :index, notice: 'You need to upgrade to create something fancy.'
92
+ end
93
+ end
94
+ ```
95
+
96
+ ### Model
97
+
98
+ Here you also need to pass a user instance to the helper. For example:
99
+
100
+ ```ruby
101
+ class Project < ActiveRecord::Base
102
+ belongs_to :account
103
+ def do_something_fancy
104
+ if feature? :something_fancy, account.users.first
105
+ # something fancy
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+ This is suboptimal and should be revisited...
112
+
113
+
114
+ ## See Also
115
+
116
+ * [Flipper](https://github.com/jnunemaker/flipper)
117
+ * [Flip](https://github.com/pda/flip)
118
+ * [Rollout](https://github.com/jamesgolick/rollout)
119
+
120
+
121
+ ## Intellectual Property
22
122
 
23
- ## Contributing
123
+ Copyright Andy Stewart, AirBlade Software. Released under the MIT licence.
24
124
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/*_test.rb']
7
+ t.verbose = true
8
+ end
data/flippeur.gemspec CHANGED
@@ -10,10 +10,12 @@ Gem::Specification.new do |gem|
10
10
  gem.email = ["boss@airbladesoftware.com"]
11
11
  gem.description = %q{Simple feature flipping.}
12
12
  gem.summary = %q{Simple feature flipping.}
13
- gem.homepage = ""
13
+ gem.homepage = "https://github.com/airblade/flippeur"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'rake', '~> 10.0.0'
19
21
  end
@@ -0,0 +1,14 @@
1
+ module Flippeur
2
+ class Feature
3
+ attr_reader :name, :block
4
+
5
+ def initialize(name, &block)
6
+ @name = name
7
+ @block = block
8
+ end
9
+
10
+ def available?(user)
11
+ block.call user
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module Flippeur
2
+
3
+ module ViewHelpers
4
+ def feature?(name)
5
+ available = Flippeur.find(name).available? @controller.current_person
6
+ block_given? ? (yield if available) : available
7
+ end
8
+ end
9
+
10
+ module ControllerHelpers
11
+ def feature?(name)
12
+ available = Flippeur.find(name).available? current_person
13
+ block_given? ? (yield if available) : available
14
+ end
15
+ end
16
+
17
+ module ModelHelpers
18
+ def feature?(name, person)
19
+ available = Flippeur.find(name).available? person
20
+ block_given? ? (yield if available) : available
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'flippeur/rails_helpers'
2
+
3
+ module Flippeur
4
+ class Railtie < Rails::Railtie
5
+ initializer 'flippeur.rails_helpers' do
6
+ ActionView::Base.send :include, Flippeur::ViewHelpers
7
+ ActionController::Base.send :include, Flippeur::ControllerHelpers
8
+ ActiveRecord::Base.send :include, Flippeur::ModelHelpers
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Flippeur
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/flippeur.rb CHANGED
@@ -1,5 +1,32 @@
1
+ require 'flippeur/railtie' if defined?(Rails)
2
+ require 'flippeur/feature'
1
3
  require "flippeur/version"
2
4
 
3
5
  module Flippeur
4
- # Your code goes here...
6
+
7
+ UnknownFeature = Class.new RuntimeError
8
+
9
+ def self.setup(&block)
10
+ reset
11
+ module_eval &block
12
+ end
13
+
14
+ def self.feature(name, &block)
15
+ features[name] = Feature.new(name, &block)
16
+ end
17
+
18
+ def self.find(name)
19
+ features[name]
20
+ end
21
+
22
+ private
23
+
24
+ def self.features
25
+ @features ||= Hash.new { |_,k| raise UnknownFeature, "Unknown feature: #{k}" }
26
+ end
27
+
28
+ def self.reset
29
+ @features = nil
30
+ end
31
+
5
32
  end
@@ -0,0 +1,63 @@
1
+ require 'minitest/autorun'
2
+ require 'flippeur'
3
+ require 'flippeur/rails_helpers'
4
+
5
+
6
+ class Controller
7
+ include Flippeur::ControllerHelpers
8
+ def current_person
9
+ Object.new
10
+ end
11
+ end
12
+
13
+
14
+ class ControllerHelpersTest < MiniTest::Unit::TestCase
15
+
16
+ def setup
17
+ @controller = Controller.new
18
+ end
19
+
20
+ def test_unknown_feature_raises_exception
21
+ Flippeur.setup { }
22
+ assert_raises Flippeur::UnknownFeature do
23
+ @controller.feature? :foo
24
+ end
25
+ end
26
+
27
+ def test_available_feature_as_boolean
28
+ Flippeur.setup do
29
+ feature(:foo) { |user| true }
30
+ end
31
+ assert @controller.feature?(:foo)
32
+ end
33
+
34
+ def test_available_feature_with_block
35
+ Flippeur.setup do
36
+ feature(:foo) { |user| true }
37
+ end
38
+ block_called = false
39
+ @controller.feature?(:foo) do
40
+ block_called = true
41
+ end
42
+ assert block_called
43
+ end
44
+
45
+ def test_unavailable_feature_as_boolean
46
+ Flippeur.setup do
47
+ feature(:foo) { |user| false }
48
+ end
49
+ refute @controller.feature?(:foo)
50
+ end
51
+
52
+ def test_unavailable_feature_with_block
53
+ Flippeur.setup do
54
+ feature(:foo) { |user| false }
55
+ end
56
+ block_called = false
57
+ @controller.feature?(:foo) do
58
+ block_called = true
59
+ end
60
+ refute block_called
61
+ end
62
+
63
+ end
@@ -0,0 +1,34 @@
1
+ require 'minitest/autorun'
2
+ require 'flippeur'
3
+
4
+ class FeatureTest < MiniTest::Unit::TestCase
5
+
6
+ def test_unknown_features_raises_exception
7
+ Flippeur.setup { }
8
+ assert_raises Flippeur::UnknownFeature do
9
+ Flippeur.find :foo
10
+ end
11
+ end
12
+
13
+ def test_finds_known_feature
14
+ Flippeur.setup do
15
+ feature(:foo) { }
16
+ end
17
+ assert_instance_of Flippeur::Feature, Flippeur.find(:foo)
18
+ end
19
+
20
+ def test_available_feature
21
+ Flippeur.setup do
22
+ feature(:foo) { |user| user.id == 42 }
23
+ end
24
+ assert Flippeur.find(:foo).available?( OpenStruct.new(id: 42) )
25
+ end
26
+
27
+ def test_unavailable_feature
28
+ Flippeur.setup do
29
+ feature(:foo) { |user| user.id == 42 }
30
+ end
31
+ refute Flippeur.find(:foo).available?( OpenStruct.new(id: 153) )
32
+ end
33
+
34
+ end
@@ -0,0 +1,60 @@
1
+ require 'minitest/autorun'
2
+ require 'flippeur'
3
+ require 'flippeur/rails_helpers'
4
+
5
+
6
+ class Model
7
+ include Flippeur::ModelHelpers
8
+ end
9
+
10
+
11
+ class ModelHelpersTest < MiniTest::Unit::TestCase
12
+
13
+ def setup
14
+ @model = Model.new
15
+ end
16
+
17
+ def test_unknown_feature_raises_exception
18
+ Flippeur.setup { }
19
+ assert_raises Flippeur::UnknownFeature do
20
+ @model.feature? :foo, Object.new
21
+ end
22
+ end
23
+
24
+ def test_available_feature_as_boolean
25
+ Flippeur.setup do
26
+ feature(:foo) { |user| true }
27
+ end
28
+ assert @model.feature?(:foo, Object.new)
29
+ end
30
+
31
+ def test_available_feature_with_block
32
+ Flippeur.setup do
33
+ feature(:foo) { |user| true }
34
+ end
35
+ block_called = false
36
+ @model.feature?(:foo, Object.new) do
37
+ block_called = true
38
+ end
39
+ assert block_called
40
+ end
41
+
42
+ def test_unavailable_feature_as_boolean
43
+ Flippeur.setup do
44
+ feature(:foo) { |user| false }
45
+ end
46
+ refute @model.feature?(:foo, Object.new)
47
+ end
48
+
49
+ def test_unavailable_feature_with_block
50
+ Flippeur.setup do
51
+ feature(:foo) { |user| false }
52
+ end
53
+ block_called = false
54
+ @model.feature?(:foo, Object.new) do
55
+ block_called = true
56
+ end
57
+ refute block_called
58
+ end
59
+
60
+ end
@@ -0,0 +1,63 @@
1
+ require 'minitest/autorun'
2
+ require 'flippeur'
3
+ require 'flippeur/rails_helpers'
4
+
5
+
6
+ class View
7
+ include Flippeur::ViewHelpers
8
+ def initialize
9
+ @controller = OpenStruct.new(current_person: Object.new)
10
+ end
11
+ end
12
+
13
+
14
+ class ViewHelpersTest < MiniTest::Unit::TestCase
15
+
16
+ def setup
17
+ @view = View.new
18
+ end
19
+
20
+ def test_unknown_feature_raises_exception
21
+ Flippeur.setup { }
22
+ assert_raises Flippeur::UnknownFeature do
23
+ @view.feature? :foo
24
+ end
25
+ end
26
+
27
+ def test_available_feature_as_boolean
28
+ Flippeur.setup do
29
+ feature(:foo) { |user| true }
30
+ end
31
+ assert @view.feature?(:foo)
32
+ end
33
+
34
+ def test_available_feature_with_block
35
+ Flippeur.setup do
36
+ feature(:foo) { |user| true }
37
+ end
38
+ block_called = false
39
+ @view.feature?(:foo) do
40
+ block_called = true
41
+ end
42
+ assert block_called
43
+ end
44
+
45
+ def test_unavailable_feature_as_boolean
46
+ Flippeur.setup do
47
+ feature(:foo) { |user| false }
48
+ end
49
+ refute @view.feature?(:foo)
50
+ end
51
+
52
+ def test_unavailable_feature_with_block
53
+ Flippeur.setup do
54
+ feature(:foo) { |user| false }
55
+ end
56
+ block_called = false
57
+ @view.feature?(:foo) do
58
+ block_called = true
59
+ end
60
+ refute block_called
61
+ end
62
+
63
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flippeur
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-20 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2013-05-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 10.0.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 10.0.0
14
30
  description: Simple feature flipping.
15
31
  email:
16
32
  - boss@airbladesoftware.com
@@ -25,8 +41,15 @@ files:
25
41
  - Rakefile
26
42
  - flippeur.gemspec
27
43
  - lib/flippeur.rb
44
+ - lib/flippeur/feature.rb
45
+ - lib/flippeur/rails_helpers.rb
46
+ - lib/flippeur/railtie.rb
28
47
  - lib/flippeur/version.rb
29
- homepage: ''
48
+ - test/controller_helpers_test.rb
49
+ - test/feature_test.rb
50
+ - test/model_helpers_test.rb
51
+ - test/view_helpers_test.rb
52
+ homepage: https://github.com/airblade/flippeur
30
53
  licenses: []
31
54
  post_install_message:
32
55
  rdoc_options: []
@@ -38,16 +61,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
61
  - - ! '>='
39
62
  - !ruby/object:Gem::Version
40
63
  version: '0'
64
+ segments:
65
+ - 0
66
+ hash: 1803748012281727158
41
67
  required_rubygems_version: !ruby/object:Gem::Requirement
42
68
  none: false
43
69
  requirements:
44
70
  - - ! '>='
45
71
  - !ruby/object:Gem::Version
46
72
  version: '0'
73
+ segments:
74
+ - 0
75
+ hash: 1803748012281727158
47
76
  requirements: []
48
77
  rubyforge_project:
49
78
  rubygems_version: 1.8.23
50
79
  signing_key:
51
80
  specification_version: 3
52
81
  summary: Simple feature flipping.
53
- test_files: []
82
+ test_files:
83
+ - test/controller_helpers_test.rb
84
+ - test/feature_test.rb
85
+ - test/model_helpers_test.rb
86
+ - test/view_helpers_test.rb