flip_fork 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 +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +2 -0
- data/README.md +161 -0
- data/Rakefile +10 -0
- data/TODO +3 -0
- data/app/assets/stylesheets/flip.css +70 -0
- data/app/controllers/flip/features_controller.rb +47 -0
- data/app/controllers/flip/strategies_controller.rb +31 -0
- data/app/helpers/flip_helper.rb +9 -0
- data/app/views/flip/features/index.html.erb +62 -0
- data/config/routes.rb +14 -0
- data/flip.gemspec +25 -0
- data/lib/flip/abstract_strategy.rb +26 -0
- data/lib/flip/controller_filters.rb +21 -0
- data/lib/flip/cookie_strategy.rb +62 -0
- data/lib/flip/database_strategy.rb +40 -0
- data/lib/flip/declarable.rb +24 -0
- data/lib/flip/declaration_strategy.rb +20 -0
- data/lib/flip/definition.rb +21 -0
- data/lib/flip/engine.rb +9 -0
- data/lib/flip/facade.rb +18 -0
- data/lib/flip/feature_set.rb +57 -0
- data/lib/flip/forbidden.rb +7 -0
- data/lib/flip/version.rb +3 -0
- data/lib/flip.rb +27 -0
- data/lib/generators/flip/install/install_generator.rb +9 -0
- data/lib/generators/flip/migration/USAGE +5 -0
- data/lib/generators/flip/migration/migration_generator.rb +22 -0
- data/lib/generators/flip/migration/templates/create_features.rb +10 -0
- data/lib/generators/flip/model/USAGE +8 -0
- data/lib/generators/flip/model/model_generator.rb +8 -0
- data/lib/generators/flip/model/templates/feature.rb +15 -0
- data/lib/generators/flip/routes/USAGE +7 -0
- data/lib/generators/flip/routes/routes_generator.rb +7 -0
- data/spec/abstract_strategy_spec.rb +11 -0
- data/spec/controller_filters_spec.rb +27 -0
- data/spec/cookie_strategy_spec.rb +112 -0
- data/spec/database_strategy_spec.rb +66 -0
- data/spec/declarable_spec.rb +32 -0
- data/spec/declaration_strategy_spec.rb +39 -0
- data/spec/definition_spec.rb +19 -0
- data/spec/feature_set_spec.rb +67 -0
- data/spec/flip_spec.rb +33 -0
- data/spec/spec_helper.rb +1 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13d411c043eedc95154d47015e144ecdbc709a19
|
4
|
+
data.tar.gz: 9aabaffe9b990127475615b59e3ba06a06c038e4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1aece50a74ff32c555fadf7cf4348cc9dcaa50ce104a23f266a9763dfc86cd16cba3c204e58bae87b86565ac401a91980d8a9f0b7498b306fced53fa2a569c97
|
7
|
+
data.tar.gz: e5a34425077ce23c1f5abb8b77c65472a2339a31502878affae1e052611ded057886d542955151c64542c4d5a2f764048214e1661ddfaec948dbc92d2ec9f14d
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
Flip — flip your features
|
2
|
+
================
|
3
|
+
|
4
|
+
[](https://travis-ci.org/pda/flip)
|
5
|
+
|
6
|
+
**Flip** provides a declarative, layered way of enabling and disabling application functionality at run-time.
|
7
|
+
|
8
|
+
This gem optimizes for:
|
9
|
+
|
10
|
+
* developer ease-of-use,
|
11
|
+
* visibility and control for other stakeholders (like marketing); and
|
12
|
+
* run-time performance
|
13
|
+
|
14
|
+
There are three layers of strategies per feature:
|
15
|
+
|
16
|
+
* default
|
17
|
+
* database, to flip features site-wide for all users
|
18
|
+
* cookie, to flip features just for you (or someone else)
|
19
|
+
|
20
|
+
There is also a configurable system-wide default - !Rails.env.production?` works nicely.
|
21
|
+
|
22
|
+
Flip has a dashboard UI that's easy to understand and use.
|
23
|
+
|
24
|
+

|
25
|
+
|
26
|
+
Install
|
27
|
+
-------
|
28
|
+
|
29
|
+
**Rails 3.0, 3.1 and 3.2+**
|
30
|
+
|
31
|
+
# Gemfile
|
32
|
+
gem "flip"
|
33
|
+
|
34
|
+
# Generate the model and migration
|
35
|
+
> rails g flip:install
|
36
|
+
|
37
|
+
# Run the migration
|
38
|
+
> rake db:migrate
|
39
|
+
|
40
|
+
|
41
|
+
Declaring Features
|
42
|
+
------------------
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# This is the model class generated by rails g flip:install
|
46
|
+
class Feature < ActiveRecord::Base
|
47
|
+
include Flip::Declarable
|
48
|
+
|
49
|
+
# The recommended Flip strategy stack.
|
50
|
+
strategy Flip::CookieStrategy
|
51
|
+
strategy Flip::DatabaseStrategy
|
52
|
+
strategy Flip::DefaultStrategy
|
53
|
+
default false
|
54
|
+
|
55
|
+
# A basic feature declaration.
|
56
|
+
feature :shiny_things
|
57
|
+
|
58
|
+
# Override the system-wide default.
|
59
|
+
feature :world_domination, default: true
|
60
|
+
|
61
|
+
# Enabled half the time..? Sure, we can do that.
|
62
|
+
feature :flakey,
|
63
|
+
default: proc { rand(2).zero? }
|
64
|
+
|
65
|
+
# Provide a description, normally derived from the feature name.
|
66
|
+
feature :something,
|
67
|
+
default: true,
|
68
|
+
description: "Ability to purchase enrollments in courses",
|
69
|
+
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
|
74
|
+
Checking Features
|
75
|
+
-----------------
|
76
|
+
|
77
|
+
`Flip.on?` or the dynamic predicate methods are used to check feature state:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Flip.on? :world_domination # true
|
81
|
+
Flip.world_domination? # true
|
82
|
+
|
83
|
+
Flip.on? :shiny_things # false
|
84
|
+
Flip.shiny_things? # false
|
85
|
+
```
|
86
|
+
|
87
|
+
Views and controllers use the `feature?(key)` method:
|
88
|
+
|
89
|
+
```erb
|
90
|
+
<div>
|
91
|
+
<% if feature? :world_domination %>
|
92
|
+
<%= link_to "Dominate World", world_dominations_path %>
|
93
|
+
<% end %>
|
94
|
+
</div>
|
95
|
+
```
|
96
|
+
|
97
|
+
|
98
|
+
Feature Flipping Controllers
|
99
|
+
----------------------------
|
100
|
+
|
101
|
+
The `Flip::ControllerFilters` module is mixed into the base `ApplicationController` class. The following controller will respond with 404 Page Not Found to all but the `index` action unless the :new_stuff feature is enabled:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class SampleController < ApplicationController
|
105
|
+
|
106
|
+
require_feature :something, :except => :index
|
107
|
+
|
108
|
+
def show
|
109
|
+
end
|
110
|
+
|
111
|
+
def index
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Dashboard
|
118
|
+
---------
|
119
|
+
|
120
|
+
The dashboard provides visibility and control over the features.
|
121
|
+
|
122
|
+
The gem includes some basic styles:
|
123
|
+
|
124
|
+
```haml
|
125
|
+
= content_for :stylesheets_head do
|
126
|
+
= stylesheet_link_tag "flip"
|
127
|
+
```
|
128
|
+
|
129
|
+
You probably don't want the dashboard to be public. Here's one way of implementing access control.
|
130
|
+
|
131
|
+
app/controllers/admin/features_controller.rb:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
class Admin::FeaturesController < Flip::FeaturesController
|
135
|
+
before_filter :assert_authenticated_as_admin
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
app/controllers/admin/feature_strategies_controller.rb:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
class Admin::FeatureStrategiesController < Flip::FeaturesController
|
143
|
+
before_filter :assert_authenticated_as_admin
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
routes.rb:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
namespace :admin do
|
151
|
+
resources :features, only: [ :index ] do
|
152
|
+
resources :feature_strategies, only: [ :update, :destroy ]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
mount Flip::Engine => "/admin/features"
|
157
|
+
```
|
158
|
+
|
159
|
+
----
|
160
|
+
Created by Paul Annesley
|
161
|
+
Copyright © 2011-2013 Learnable Pty Ltd, [MIT Licence](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
/* Flip */
|
2
|
+
|
3
|
+
.flip {
|
4
|
+
margin: 0;
|
5
|
+
}
|
6
|
+
|
7
|
+
.flip h1 {
|
8
|
+
color:#666666;
|
9
|
+
font-size: 229%;
|
10
|
+
line-height: 44.928px;
|
11
|
+
margin: 13.5px 0;
|
12
|
+
}
|
13
|
+
|
14
|
+
.flip th.name, .flip th.description, .flip th.status {
|
15
|
+
visibility: hidden;
|
16
|
+
}
|
17
|
+
|
18
|
+
.flip td.name {
|
19
|
+
font-family: Monaco, sans-serif;
|
20
|
+
font-weight: bold;
|
21
|
+
}
|
22
|
+
|
23
|
+
.flip td.name, .flip td.description {
|
24
|
+
vertical-align: top;
|
25
|
+
}
|
26
|
+
|
27
|
+
.flip th {
|
28
|
+
font-weight: normal;
|
29
|
+
text-align: left;
|
30
|
+
vertical-align: top;
|
31
|
+
}
|
32
|
+
|
33
|
+
.flip th .description {
|
34
|
+
font-weight: normal;
|
35
|
+
display: block;
|
36
|
+
font-size: 80%;
|
37
|
+
}
|
38
|
+
|
39
|
+
.flip th, .flip td {
|
40
|
+
padding: 5px 10px;
|
41
|
+
width: 160px;
|
42
|
+
height: 40px;
|
43
|
+
}
|
44
|
+
|
45
|
+
.flip td.off, .flip td.on, .flip td.pass {
|
46
|
+
text-align: center;
|
47
|
+
text-transform: capitalize;
|
48
|
+
}
|
49
|
+
|
50
|
+
.flip td.off {
|
51
|
+
background-color: #fbb;
|
52
|
+
}
|
53
|
+
|
54
|
+
.flip td.on {
|
55
|
+
background-color: #cfc;
|
56
|
+
}
|
57
|
+
|
58
|
+
.flip td.pass {
|
59
|
+
background-color: #eef;
|
60
|
+
}
|
61
|
+
|
62
|
+
.flip form {
|
63
|
+
display: inline;
|
64
|
+
}
|
65
|
+
|
66
|
+
.flip form input[type=submit] {
|
67
|
+
font-size: 80%;
|
68
|
+
padding: 2px 5px;
|
69
|
+
margin: 0;
|
70
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Flip
|
2
|
+
class FeaturesController < ApplicationController
|
3
|
+
|
4
|
+
def index
|
5
|
+
@p = FeaturesPresenter.new(FeatureSet.instance)
|
6
|
+
end
|
7
|
+
|
8
|
+
class FeaturesPresenter
|
9
|
+
|
10
|
+
include Flip::Engine.routes.url_helpers
|
11
|
+
|
12
|
+
def initialize(feature_set)
|
13
|
+
@feature_set = feature_set
|
14
|
+
end
|
15
|
+
|
16
|
+
def strategies
|
17
|
+
@feature_set.strategies
|
18
|
+
end
|
19
|
+
|
20
|
+
def definitions
|
21
|
+
@feature_set.definitions
|
22
|
+
end
|
23
|
+
|
24
|
+
def status(definition)
|
25
|
+
@feature_set.on?(definition.key) ? "on" : "off"
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_status(definition)
|
29
|
+
@feature_set.default_for(definition) ? "on" : "off"
|
30
|
+
end
|
31
|
+
|
32
|
+
def strategy_status(strategy, definition)
|
33
|
+
if strategy.knows? definition
|
34
|
+
strategy.on?(definition) ? "on" : "off"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def switch_url(strategy, definition)
|
39
|
+
feature_strategy_path \
|
40
|
+
definition.key,
|
41
|
+
strategy.name.underscore
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Flip
|
2
|
+
class StrategiesController < ApplicationController
|
3
|
+
|
4
|
+
include Flip::Engine.routes.url_helpers
|
5
|
+
|
6
|
+
def update
|
7
|
+
strategy.switch! feature_key, turn_on?
|
8
|
+
redirect_to features_url
|
9
|
+
end
|
10
|
+
|
11
|
+
def destroy
|
12
|
+
strategy.delete! feature_key
|
13
|
+
redirect_to features_url
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def turn_on?
|
19
|
+
params[:commit] == "Switch On"
|
20
|
+
end
|
21
|
+
|
22
|
+
def feature_key
|
23
|
+
params[:feature_id].to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
def strategy
|
27
|
+
FeatureSet.instance.strategy(params[:id])
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<div class="flip">
|
2
|
+
<h1>Feature Flippers</h1>
|
3
|
+
|
4
|
+
<table>
|
5
|
+
<thead>
|
6
|
+
<th class="name">Feature Name</th>
|
7
|
+
<th class="description">Description</th>
|
8
|
+
<th class="status">Status</th>
|
9
|
+
<% @p.strategies.each do |strategy| %>
|
10
|
+
<th>
|
11
|
+
<%= strategy.name %>
|
12
|
+
<span class="description"><%= strategy.description %></span>
|
13
|
+
</th>
|
14
|
+
<% end %>
|
15
|
+
<th>
|
16
|
+
Default
|
17
|
+
<span class="description">The system default when no strategies match.</span>
|
18
|
+
</th>
|
19
|
+
</thead>
|
20
|
+
<tbody>
|
21
|
+
<% @p.definitions.each do |definition| %>
|
22
|
+
<tr>
|
23
|
+
<td class="name"><%= definition.name %></td>
|
24
|
+
|
25
|
+
<td class="description"><%= definition.description %></td>
|
26
|
+
|
27
|
+
<%= content_tag :td, class: @p.status(definition) do %>
|
28
|
+
<%= @p.status definition %>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% @p.strategies.each do |strategy| %>
|
32
|
+
<%= content_tag :td, class: @p.strategy_status(strategy, definition) || "pass" do %>
|
33
|
+
<%= @p.strategy_status strategy, definition %>
|
34
|
+
|
35
|
+
<% if strategy.switchable? %>
|
36
|
+
<%= form_tag(@p.switch_url(strategy, definition), method: :put) do %>
|
37
|
+
<% unless @p.strategy_status(strategy, definition) == "on" %>
|
38
|
+
<%= submit_tag "Switch On" %>
|
39
|
+
<% end %>
|
40
|
+
<% unless @p.strategy_status(strategy, definition) == "off" %>
|
41
|
+
<%= submit_tag "Switch Off" %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
<% unless @p.strategy_status(strategy, definition).blank? %>
|
45
|
+
<%= form_tag(@p.switch_url(strategy, definition), method: :delete) do %>
|
46
|
+
<%= submit_tag "Delete" %>
|
47
|
+
<% end %>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
<%= content_tag :td, class: @p.default_status(definition) do %>
|
55
|
+
<%= @p.default_status definition %>
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
</tr>
|
59
|
+
<% end %>
|
60
|
+
</tbody>
|
61
|
+
</table>
|
62
|
+
</div>
|
data/config/routes.rb
ADDED
data/flip.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "flip/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "flip_fork"
|
7
|
+
s.version = Flip::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Paul Annesley", "Brandt Lareau"]
|
10
|
+
s.email = ["paul@annesley.cc, brandt.lareau@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/newdark/flip"
|
12
|
+
s.summary = %q{A feature flipper for Rails web applications.}
|
13
|
+
s.description = %q{Declarative API for specifying features, switchable in declaration, database and cookies.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency("activesupport", "~> 3.0")
|
21
|
+
s.add_dependency("i18n")
|
22
|
+
|
23
|
+
s.add_development_dependency("rspec", "~> 2.5")
|
24
|
+
s.add_development_dependency("rake")
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Flip
|
2
|
+
class AbstractStrategy
|
3
|
+
|
4
|
+
def name
|
5
|
+
self.class.name.split("::").last.gsub(/Strategy$/, "").underscore
|
6
|
+
end
|
7
|
+
|
8
|
+
def description; ""; end
|
9
|
+
|
10
|
+
# Whether the strategy knows the on/off state of the switch.
|
11
|
+
def knows? definition; raise; end
|
12
|
+
|
13
|
+
# Given the state is known, whether it is on or off.
|
14
|
+
def on? definition; raise; end
|
15
|
+
|
16
|
+
# Whether the feature can be switched on and off at runtime.
|
17
|
+
# If true, the strategy must also respond to switch! and delete!
|
18
|
+
def switchable?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def switch! key, on; raise; end
|
23
|
+
def delete! key; raise; end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Flip
|
2
|
+
module ControllerFilters
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def require_feature key, options = {}
|
9
|
+
before_filter options do
|
10
|
+
flip_feature_disabled key unless Flip.on? key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def flip_feature_disabled key
|
17
|
+
raise Flip::Forbidden.new(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Uses cookie to determine feature state.
|
2
|
+
module Flip
|
3
|
+
class CookieStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Uses cookies to apply only to your session."
|
7
|
+
end
|
8
|
+
|
9
|
+
def knows? definition
|
10
|
+
cookies.key? cookie_name(definition)
|
11
|
+
end
|
12
|
+
|
13
|
+
def on? definition
|
14
|
+
cookies[cookie_name(definition)] === "true"
|
15
|
+
end
|
16
|
+
|
17
|
+
def switchable?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def switch! key, on
|
22
|
+
cookies[cookie_name(key)] = on ? "true" : "false"
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete! key
|
26
|
+
cookies.delete cookie_name(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.cookies= cookies
|
30
|
+
@cookies = cookies
|
31
|
+
end
|
32
|
+
|
33
|
+
def cookie_name(definition)
|
34
|
+
definition = definition.key unless definition.is_a? Symbol
|
35
|
+
"flip_#{definition}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def cookies
|
41
|
+
self.class.instance_variable_get(:@cookies) || {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Include in ApplicationController to push cookies into CookieStrategy.
|
45
|
+
# Users before_filter and after_filter rather than around_filter to
|
46
|
+
# avoid pointlessly adding to stack depth.
|
47
|
+
module Loader
|
48
|
+
extend ActiveSupport::Concern
|
49
|
+
included do
|
50
|
+
before_filter :flip_cookie_strategy_before
|
51
|
+
after_filter :flip_cookie_strategy_after
|
52
|
+
end
|
53
|
+
def flip_cookie_strategy_before
|
54
|
+
CookieStrategy.cookies = cookies
|
55
|
+
end
|
56
|
+
def flip_cookie_strategy_after
|
57
|
+
CookieStrategy.cookies = nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Database backed system-wide
|
2
|
+
module Flip
|
3
|
+
class DatabaseStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def initialize(model_klass = Feature)
|
6
|
+
@klass = model_klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def description
|
10
|
+
"Database backed, applies to all users."
|
11
|
+
end
|
12
|
+
|
13
|
+
def knows? definition
|
14
|
+
!!feature(definition)
|
15
|
+
end
|
16
|
+
|
17
|
+
def on? definition
|
18
|
+
feature(definition).enabled?
|
19
|
+
end
|
20
|
+
|
21
|
+
def switchable?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def switch! key, enable
|
26
|
+
@klass.find_or_initialize_by_key(key.to_s).update_attributes! enabled: enable
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete! key
|
30
|
+
@klass.find_by_key(key.to_s).try(:destroy)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def feature(definition)
|
36
|
+
@klass.find_by_key definition.key.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Flip
|
2
|
+
module Declarable
|
3
|
+
|
4
|
+
def self.extended(base)
|
5
|
+
FeatureSet.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
# Adds a new feature definition, creates predicate method.
|
9
|
+
def feature(key, options = {})
|
10
|
+
FeatureSet.instance << Flip::Definition.new(key, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds a strategy for determining feature status.
|
14
|
+
def strategy(strategy)
|
15
|
+
FeatureSet.instance.add_strategy strategy
|
16
|
+
end
|
17
|
+
|
18
|
+
# The default response, boolean or a Proc to be called.
|
19
|
+
def default(default)
|
20
|
+
FeatureSet.instance.default = default
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Uses :default option passed to feature declaration.
|
2
|
+
# May be boolean or a Proc to be passed the definition.
|
3
|
+
module Flip
|
4
|
+
class DeclarationStrategy < AbstractStrategy
|
5
|
+
|
6
|
+
def description
|
7
|
+
"The default status declared with the feature."
|
8
|
+
end
|
9
|
+
|
10
|
+
def knows? definition
|
11
|
+
!definition.options[:default].nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def on? definition
|
15
|
+
default = definition.options[:default]
|
16
|
+
default.is_a?(Proc) ? default.call(definition) : default
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Flip
|
2
|
+
class Definition
|
3
|
+
|
4
|
+
attr_accessor :key
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(key, options = {})
|
8
|
+
@key = key
|
9
|
+
@options = options.reverse_merge \
|
10
|
+
description: key.to_s.humanize + "."
|
11
|
+
end
|
12
|
+
|
13
|
+
alias :name :key
|
14
|
+
alias :to_s :key
|
15
|
+
|
16
|
+
def description
|
17
|
+
options[:description]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|