flip2 1.1.1
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/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/README.md +180 -0
- data/Rakefile +11 -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 +27 -0
- data/lib/flip/abstract_strategy.rb +26 -0
- data/lib/flip/cacheable.rb +25 -0
- data/lib/flip/controller_filters.rb +25 -0
- data/lib/flip/cookie_strategy.rb +67 -0
- data/lib/flip/database_strategy.rb +46 -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/flip2.rb +28 -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 +11 -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/lib/generators/flip/views/USAGE +8 -0
- data/lib/generators/flip/views/templates/index.html.erb +54 -0
- data/lib/generators/flip/views/views_generator.rb +8 -0
- data/spec/abstract_strategy_spec.rb +11 -0
- data/spec/cacheable_spec.rb +49 -0
- data/spec/controller_filters_spec.rb +27 -0
- data/spec/cookie_strategy_spec.rb +112 -0
- data/spec/database_strategy_spec.rb +110 -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 +2 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 25447912d81c33420156bf08bbbb30b1e82fb60f
|
4
|
+
data.tar.gz: 329f735ba5dc4f9b79141be8e6f61e3e52320713
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39ba6d8717253a8b1780c59d0b31bed522ba0ce8408212d80e99c1f33a84f85e0d8ee19dc3cc3da69e69aedca8b8694c45dd113377dd25f028b89016096a7716
|
7
|
+
data.tar.gz: f0267a9848aa22d6e43ecfbd8fadd90c0b8dce38572d1e13e6f20a0f8f94d4e2ec8016c3274f067765e62c9622c2bc8c28b2629e83455dd1cea9f99e823bbb43
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
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
|
+
# Include the Feature model, e.g. config/initializers/feature.rb:
|
41
|
+
require 'feature'
|
42
|
+
|
43
|
+
Declaring Features
|
44
|
+
------------------
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# This is the model class generated by rails g flip:install
|
48
|
+
class Feature < ActiveRecord::Base
|
49
|
+
extend Flip::Declarable
|
50
|
+
|
51
|
+
# The recommended Flip strategy stack.
|
52
|
+
strategy Flip::CookieStrategy
|
53
|
+
strategy Flip::DatabaseStrategy
|
54
|
+
strategy Flip::DefaultStrategy
|
55
|
+
default false
|
56
|
+
|
57
|
+
# A basic feature declaration.
|
58
|
+
feature :shiny_things
|
59
|
+
|
60
|
+
# Override the system-wide default.
|
61
|
+
feature :world_domination, default: true
|
62
|
+
|
63
|
+
# Enabled half the time..? Sure, we can do that.
|
64
|
+
feature :flakey,
|
65
|
+
default: proc { rand(2).zero? }
|
66
|
+
|
67
|
+
# Provide a description, normally derived from the feature name.
|
68
|
+
feature :something,
|
69
|
+
default: true,
|
70
|
+
description: "Ability to purchase enrollments in courses"
|
71
|
+
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
|
76
|
+
Checking Features
|
77
|
+
-----------------
|
78
|
+
|
79
|
+
`Flip.on?` or the dynamic predicate methods are used to check feature state:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Flip.on? :world_domination # true
|
83
|
+
Flip.world_domination? # true
|
84
|
+
|
85
|
+
Flip.on? :shiny_things # false
|
86
|
+
Flip.shiny_things? # false
|
87
|
+
```
|
88
|
+
|
89
|
+
Views and controllers use the `feature?(key)` method:
|
90
|
+
|
91
|
+
```erb
|
92
|
+
<div>
|
93
|
+
<% if feature? :world_domination %>
|
94
|
+
<%= link_to "Dominate World", world_dominations_path %>
|
95
|
+
<% end %>
|
96
|
+
</div>
|
97
|
+
```
|
98
|
+
|
99
|
+
|
100
|
+
Feature Flipping Controllers
|
101
|
+
----------------------------
|
102
|
+
|
103
|
+
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 `:something` feature is enabled:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class SampleController < ApplicationController
|
107
|
+
|
108
|
+
require_feature :something, :except => :index
|
109
|
+
|
110
|
+
def show
|
111
|
+
end
|
112
|
+
|
113
|
+
def index
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
Dashboard
|
120
|
+
---------
|
121
|
+
|
122
|
+
The dashboard provides visibility and control over the features.
|
123
|
+
|
124
|
+
The gem includes some basic styles:
|
125
|
+
|
126
|
+
```haml
|
127
|
+
= content_for :stylesheets_head do
|
128
|
+
= stylesheet_link_tag "flip"
|
129
|
+
```
|
130
|
+
|
131
|
+
You probably don't want the dashboard to be public. Here's one way of implementing access control.
|
132
|
+
|
133
|
+
app/controllers/admin/features_controller.rb:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class Admin::FeaturesController < Flip::FeaturesController
|
137
|
+
before_action :assert_authenticated_as_admin
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
app/controllers/admin/strategies_controller.rb:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Admin::StrategiesController < Flip::StrategiesController
|
145
|
+
before_action :assert_authenticated_as_admin
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
routes.rb:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
namespace :admin do
|
153
|
+
resources :features, only: [ :index ] do
|
154
|
+
resources :strategies, only: [ :update, :destroy ]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
mount Flip::Engine => "/admin/features"
|
159
|
+
```
|
160
|
+
|
161
|
+
Cacheable
|
162
|
+
---------
|
163
|
+
|
164
|
+
You can optimize your feature to ensure that it doesn't make a ton of feature
|
165
|
+
calls by adding Cacheable to your model.
|
166
|
+
```ruby
|
167
|
+
extend Flip::Cacheable
|
168
|
+
```
|
169
|
+
|
170
|
+
This will ensure that your features are eager loaded with one call to the database
|
171
|
+
instead of every call to Flip#on? generating a call to the database. This is
|
172
|
+
helpful if you have a larger Rails application and more than a few features
|
173
|
+
defined.
|
174
|
+
|
175
|
+
To start or reset the cache, just call #start_feature_cache.
|
176
|
+
|
177
|
+
|
178
|
+
----
|
179
|
+
Created by Paul Annesley
|
180
|
+
Copyright © 2011-2013 Learnable Pty Ltd, [MIT Licence](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
desc "Run all tests"
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc "Run specs"
|
8
|
+
task :spec do
|
9
|
+
command = "bundle exec rspec --color --format documentation spec/*_spec.rb"
|
10
|
+
system(command) || raise("specs returned non-zero code")
|
11
|
+
end
|
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>
|