flippeur 0.0.1 → 1.0.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/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