flipflop 2.3.1 → 2.4.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 +5 -5
- data/.travis.yml +23 -12
- data/CHANGES.md +5 -0
- data/Gemfile +1 -1
- data/README.md +5 -0
- data/app/controllers/flipflop/strategies_controller.rb +4 -4
- data/lib/flipflop/feature_definition.rb +7 -2
- data/lib/flipflop/feature_set.rb +37 -14
- data/lib/flipflop/version.rb +1 -1
- data/lib/generators/flipflop/install/install_generator.rb +15 -14
- data/test/integration/app_test.rb +17 -0
- data/test/test_helper.rb +11 -1
- data/test/unit/feature_definition_test.rb +26 -0
- data/test/unit/strategies/abstract_strategy_test.rb +7 -1
- data/test/unit/strategies_controller_test.rb +8 -0
- metadata +3 -5
- data/lib/test_engine/config/features.rb +0 -3
- data/lib/test_engine/test_engine.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2d991f9cd5e93e4a837c0e6835da5ae2339498bf74a26fc6e21cee4503793ca6
|
4
|
+
data.tar.gz: 943ba8e6ea38bf2afbc45454890f8111a3b9029383018e5ddf3a76f8faac0732
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a81cbf3b37f9d91ec310bc971eec5c06b2d4b0581bd42191366503782bb8d95ef6b51fef33588bd28ec571a57ebc03f51cd1c5f9514ac8151f1a855e4072e085
|
7
|
+
data.tar.gz: 30a184287b36b7a346cc72fb0bf4f2ed4cb59083461f3859d70d1ec9908c37b3c1b65e54dc3536ed472fed85f94465ea191c6d4eeb7b049679b4056cb3585b05
|
data/.travis.yml
CHANGED
@@ -1,38 +1,49 @@
|
|
1
1
|
language: ruby
|
2
|
+
cache: bundler
|
2
3
|
rvm:
|
3
|
-
- 2.
|
4
|
-
- 2.
|
5
|
-
-
|
4
|
+
- 2.5.0
|
5
|
+
- 2.4.2
|
6
|
+
- 2.3.5
|
7
|
+
- jruby-9.1.13.0
|
6
8
|
- ruby-head
|
7
9
|
- jruby-head
|
8
10
|
env:
|
9
11
|
- RAILS_VERSION=master
|
10
12
|
- RAILS_VERSION=master RAILS_API_ONLY=1
|
13
|
+
- RAILS_VERSION=5.2
|
14
|
+
- RAILS_VERSION=5.2 RAILS_API_ONLY=1
|
15
|
+
- RAILS_VERSION=5.1
|
16
|
+
- RAILS_VERSION=5.1 RAILS_API_ONLY=1
|
11
17
|
- RAILS_VERSION=5.0
|
12
18
|
- RAILS_VERSION=5.0 RAILS_API_ONLY=1
|
13
19
|
matrix:
|
14
20
|
include:
|
15
|
-
- rvm: 2.3.
|
21
|
+
- rvm: 2.3.5
|
16
22
|
env: RAILS_VERSION=4.2
|
17
|
-
- rvm: 2.2.
|
23
|
+
- rvm: 2.2.8
|
18
24
|
env: RAILS_VERSION=4.2
|
19
|
-
- rvm: 2.1.
|
25
|
+
- rvm: 2.1.10
|
20
26
|
env: RAILS_VERSION=4.2
|
21
27
|
- rvm: 2.0.0
|
22
28
|
env: RAILS_VERSION=4.2
|
23
|
-
- rvm: 2.3.
|
29
|
+
- rvm: 2.3.5
|
24
30
|
env: RAILS_VERSION=4.1
|
25
|
-
- rvm: 2.2.
|
31
|
+
- rvm: 2.2.8
|
26
32
|
env: RAILS_VERSION=4.1
|
27
|
-
- rvm: 2.1.
|
33
|
+
- rvm: 2.1.10
|
28
34
|
env: RAILS_VERSION=4.1
|
29
35
|
- rvm: 2.0.0
|
30
36
|
env: RAILS_VERSION=4.1
|
37
|
+
exclude:
|
38
|
+
- rvm: 2.3.5
|
39
|
+
env: RAILS_VERSION=master
|
40
|
+
- rvm: 2.3.5
|
41
|
+
env: RAILS_VERSION=master RAILS_API_ONLY=1
|
31
42
|
allow_failures:
|
32
|
-
- rvm: jruby-9.1.
|
43
|
+
- rvm: jruby-9.1.13.0
|
33
44
|
- rvm: ruby-head
|
34
45
|
- rvm: jruby-head
|
35
|
-
before_deploy: rake assets:compile
|
46
|
+
before_deploy: bundle exec rake assets:compile
|
36
47
|
deploy:
|
37
48
|
provider: rubygems
|
38
49
|
api_key:
|
@@ -41,4 +52,4 @@ deploy:
|
|
41
52
|
on:
|
42
53
|
tags: true
|
43
54
|
repo: voormedia/flipflop
|
44
|
-
ruby: 2.3.
|
55
|
+
ruby: 2.3.5
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 2.4.0
|
2
|
+
|
3
|
+
* Add location of feature definition.
|
4
|
+
* Raise error for undefined feature or strategy keys. This change can potentially break test cases that use dummy keys.
|
5
|
+
|
1
6
|
## 2.3.1
|
2
7
|
|
3
8
|
* Fixed backwards compatibility of strategies controller. The incompatibility was introduced in 2.3.0. (@jcoyne)
|
data/Gemfile
CHANGED
@@ -15,7 +15,7 @@ group :test do
|
|
15
15
|
gem "fakeredis", require: false
|
16
16
|
gem "sqlite3", ">= 1.3", platform: :ruby
|
17
17
|
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby,
|
18
|
-
github: "jruby/activerecord-jdbc-adapter"
|
18
|
+
github: "jruby/activerecord-jdbc-adapter"
|
19
19
|
|
20
20
|
gem "minitest", ">= 4.2"
|
21
21
|
gem "capybara", ">= 2.6"
|
data/README.md
CHANGED
@@ -78,6 +78,11 @@ end
|
|
78
78
|
This file is automatically reloaded in development mode. No need to restart
|
79
79
|
your server after making changes.
|
80
80
|
|
81
|
+
Feature definitions support these options:
|
82
|
+
* `:default` – The feature's default value. This is the value of the feature if no strategy configures an explicit value. Defaults to `false`.
|
83
|
+
* `:description` – An optional description of the feature. Displayed on the dashboard if present.
|
84
|
+
* `:title` – An optional title of the feature. This defaults to a humanized version of the feature name. Displayed on the dashboard.
|
85
|
+
|
81
86
|
## Strategies
|
82
87
|
|
83
88
|
The following strategies are provided:
|
@@ -3,12 +3,12 @@ module Flipflop
|
|
3
3
|
include ActionController::RequestForgeryProtection
|
4
4
|
|
5
5
|
def update
|
6
|
-
|
6
|
+
FeatureSet.current.switch!(feature_key, strategy_key, enable?)
|
7
7
|
redirect_to(features_url)
|
8
8
|
end
|
9
9
|
|
10
10
|
def destroy
|
11
|
-
|
11
|
+
FeatureSet.current.clear!(feature_key, strategy_key)
|
12
12
|
redirect_to(features_url)
|
13
13
|
end
|
14
14
|
|
@@ -27,8 +27,8 @@ module Flipflop
|
|
27
27
|
params[:feature_id].to_sym
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
30
|
+
def strategy_key
|
31
|
+
params[:id]
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -1,14 +1,19 @@
|
|
1
1
|
module Flipflop
|
2
2
|
class FeatureDefinition
|
3
|
-
attr_reader :key, :name, :title, :description, :default, :group
|
3
|
+
attr_reader :key, :name, :title, :description, :default, :group, :location
|
4
4
|
|
5
5
|
def initialize(key, **options)
|
6
6
|
@key = key
|
7
7
|
@name = @key.to_s.freeze
|
8
|
-
@title = @name.humanize.freeze
|
8
|
+
@title = options.delete(:title).freeze || @name.humanize.freeze
|
9
9
|
@description = options.delete(:description).freeze
|
10
10
|
@default = !!options.delete(:default) || false
|
11
11
|
@group = options.delete(:group).freeze
|
12
|
+
@location = caller_locations(3, 1).first.freeze
|
13
|
+
|
14
|
+
if options.any?
|
15
|
+
raise FeatureError.new(name, "has unknown option #{options.keys.map(&:inspect) * ', '}")
|
16
|
+
end
|
12
17
|
end
|
13
18
|
end
|
14
19
|
end
|
data/lib/flipflop/feature_set.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
module Flipflop
|
2
2
|
class FeatureError < StandardError
|
3
|
-
def initialize(
|
4
|
-
super("Feature '#{
|
3
|
+
def initialize(key, error)
|
4
|
+
super("Feature '#{key}' #{error}.")
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
8
|
class StrategyError < StandardError
|
9
|
-
def initialize(
|
10
|
-
super("Strategy '#{
|
9
|
+
def initialize(key, error)
|
10
|
+
super("Strategy '#{key}' #{error}.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Callback < StandardError
|
15
|
+
def initialize(key, error)
|
16
|
+
super("Callback '#{key}' #{error}.")
|
11
17
|
end
|
12
18
|
end
|
13
19
|
|
@@ -70,19 +76,22 @@ module Flipflop
|
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
73
|
-
def enabled?(
|
74
|
-
FeatureCache.current.fetch(
|
79
|
+
def enabled?(feature_key)
|
80
|
+
FeatureCache.current.fetch(feature_key) do
|
81
|
+
feature = feature(feature_key)
|
82
|
+
|
75
83
|
result = @strategies.each_value.inject(nil) do |status, strategy|
|
76
84
|
break status unless status.nil?
|
77
|
-
strategy.enabled?(
|
85
|
+
strategy.enabled?(feature_key)
|
78
86
|
end
|
79
|
-
|
87
|
+
|
88
|
+
result.nil? ? feature.default : result
|
80
89
|
end
|
81
90
|
end
|
82
91
|
|
83
|
-
def feature(
|
84
|
-
@features.fetch(
|
85
|
-
raise FeatureError.new(
|
92
|
+
def feature(feature_key)
|
93
|
+
@features.fetch(feature_key) do
|
94
|
+
raise FeatureError.new(feature_key, "unknown")
|
86
95
|
end
|
87
96
|
end
|
88
97
|
|
@@ -90,14 +99,28 @@ module Flipflop
|
|
90
99
|
@features.values
|
91
100
|
end
|
92
101
|
|
93
|
-
def strategy(
|
94
|
-
@strategies.fetch(
|
95
|
-
raise StrategyError.new(
|
102
|
+
def strategy(strategy_key)
|
103
|
+
@strategies.fetch(strategy_key) do
|
104
|
+
raise StrategyError.new(strategy_key, "unknown")
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
99
108
|
def strategies
|
100
109
|
@strategies.values
|
101
110
|
end
|
111
|
+
|
112
|
+
def switch!(feature_key, strategy_key, value)
|
113
|
+
strategy = strategy(strategy_key)
|
114
|
+
feature = feature(feature_key)
|
115
|
+
|
116
|
+
strategy.switch!(feature.key, value)
|
117
|
+
end
|
118
|
+
|
119
|
+
def clear!(feature_key, strategy_key)
|
120
|
+
strategy = strategy(strategy_key)
|
121
|
+
feature = feature(feature_key)
|
122
|
+
|
123
|
+
strategy.clear!(feature.key)
|
124
|
+
end
|
102
125
|
end
|
103
126
|
end
|
data/lib/flipflop/version.rb
CHANGED
@@ -10,26 +10,27 @@ class Flipflop::InstallGenerator < Rails::Generators::Base
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def configure_dashboard
|
13
|
-
|
14
|
-
|
15
|
-
# to implement access control for the Flipflop dashboard.
|
16
|
-
RUBY
|
17
|
-
|
18
|
-
forbidden = <<-RUBY
|
19
|
-
config.flipflop.dashboard_access_filter = -> { head :forbidden }
|
20
|
-
RUBY
|
21
|
-
|
22
|
-
allowed = <<-RUBY
|
23
|
-
config.flipflop.dashboard_access_filter = nil
|
24
|
-
RUBY
|
13
|
+
app = tmpl("-> { head :forbidden }")
|
14
|
+
env_dev_test = tmpl("nil")
|
25
15
|
|
26
|
-
environment(indent(
|
27
|
-
environment(indent(
|
16
|
+
environment(indent(app + "\n", 4).lstrip)
|
17
|
+
environment(indent(env_dev_test + "\n", 2).lstrip, env: [:development, :test])
|
28
18
|
end
|
29
19
|
|
30
20
|
private
|
31
21
|
|
22
|
+
def tmpl(access_filter)
|
23
|
+
return <<-RUBY
|
24
|
+
# Before filter for Flipflop dashboard. Replace with a lambda or method name
|
25
|
+
# defined in ApplicationController to implement access control.
|
26
|
+
config.flipflop.dashboard_access_filter = #{access_filter}
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
|
32
30
|
def indent(content, multiplier = 2)
|
31
|
+
# Don't fix indentation if Rails already does this (5.2+).
|
32
|
+
return content if respond_to?(:optimize_indentation, true)
|
33
|
+
|
33
34
|
spaces = " " * multiplier
|
34
35
|
content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
|
35
36
|
end
|
@@ -14,6 +14,23 @@ describe Flipflop do
|
|
14
14
|
@app
|
15
15
|
end
|
16
16
|
|
17
|
+
describe "configuration" do
|
18
|
+
it "should be added to dev" do
|
19
|
+
assert_match %r{^ config\.flipflop\.dashboard_access_filter = nil$},
|
20
|
+
File.read("config/environments/development.rb")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be added to test" do
|
24
|
+
assert_match %r{^ config\.flipflop\.dashboard_access_filter = nil$},
|
25
|
+
File.read("config/environments/test.rb")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be added to app" do
|
29
|
+
assert_match %r{^ config\.flipflop\.dashboard_access_filter = -> \{ head :forbidden \}$},
|
30
|
+
File.read("config/application.rb")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
17
34
|
describe "middleware" do
|
18
35
|
it "should include cache middleware" do
|
19
36
|
middlewares = Rails.application.middleware.map(&:klass)
|
data/test/test_helper.rb
CHANGED
@@ -110,6 +110,10 @@ class TestApp
|
|
110
110
|
skip_turbolinks: true,
|
111
111
|
).invoke_all
|
112
112
|
|
113
|
+
# Remove bootsnap if present, this interferes with reloading apps.
|
114
|
+
boot_path = File.expand_path("../../" + path + "/config/boot.rb", __FILE__)
|
115
|
+
File.write(boot_path, File.read(boot_path).gsub("require 'bootsnap/setup'", ""))
|
116
|
+
|
113
117
|
Flipflop::InstallGenerator.new([],
|
114
118
|
quiet: true,
|
115
119
|
).invoke_all
|
@@ -141,7 +145,13 @@ class TestApp
|
|
141
145
|
|
142
146
|
silence_stdout { ActiveRecord::Tasks::DatabaseTasks.create_current }
|
143
147
|
ActiveRecord::Migration.verbose = false
|
144
|
-
|
148
|
+
|
149
|
+
if defined?(ActiveRecord::Migrator.migrate)
|
150
|
+
ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"].to_a)
|
151
|
+
else
|
152
|
+
# Rails 5.2+
|
153
|
+
ActiveRecord::MigrationContext.new(Rails.application.paths["db/migrate"]).migrate
|
154
|
+
end
|
145
155
|
end
|
146
156
|
|
147
157
|
def unload!
|
@@ -29,6 +29,11 @@ describe Flipflop::FeatureDefinition do
|
|
29
29
|
it "should have no group" do
|
30
30
|
assert_nil subject.group
|
31
31
|
end
|
32
|
+
|
33
|
+
it "should have location" do
|
34
|
+
# Because we have no indirection via FeatureSet, the location is minitest.
|
35
|
+
assert_equal "instance_eval", subject.location.label
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
describe "with options" do
|
@@ -63,5 +68,26 @@ describe Flipflop::FeatureDefinition do
|
|
63
68
|
it "should have specified group" do
|
64
69
|
assert_equal :my_group, subject.group.key
|
65
70
|
end
|
71
|
+
|
72
|
+
it "should have location" do
|
73
|
+
# Because we have no indirection via FeatureSet, the location is minitest.
|
74
|
+
assert_equal "instance_eval", subject.location.label
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "with unknown options" do
|
79
|
+
subject do
|
80
|
+
Flipflop::FeatureDefinition.new(:my_key,
|
81
|
+
unknown: "one",
|
82
|
+
other: "two",
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should raise error with message" do
|
87
|
+
error = assert_raises Flipflop::FeatureError do
|
88
|
+
subject
|
89
|
+
end
|
90
|
+
assert_equal "Feature 'my_key' has unknown option :unknown, :other.", error.message
|
91
|
+
end
|
66
92
|
end
|
67
93
|
end
|
@@ -51,7 +51,13 @@ describe Flipflop::Strategies::AbstractStrategy do
|
|
51
51
|
it "should raise if request is missing in thread" do
|
52
52
|
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = 3
|
53
53
|
assert_raises Flipflop::StrategyError do
|
54
|
-
Thread.new {
|
54
|
+
Thread.new {
|
55
|
+
if Thread.current.respond_to?(:report_on_exception)
|
56
|
+
Thread.current.report_on_exception = false
|
57
|
+
end
|
58
|
+
|
59
|
+
subject.send(:request)
|
60
|
+
}.value
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipflop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Annesley
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2018-04-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -82,8 +82,6 @@ files:
|
|
82
82
|
- lib/generators/flipflop/migration/templates/create_features.rb
|
83
83
|
- lib/generators/flipflop/routes/USAGE
|
84
84
|
- lib/generators/flipflop/routes/routes_generator.rb
|
85
|
-
- lib/test_engine/config/features.rb
|
86
|
-
- lib/test_engine/test_engine.rb
|
87
85
|
- src/stylesheets/_flipflop.scss
|
88
86
|
- test/integration/app_test.rb
|
89
87
|
- test/integration/dashboard_test.rb
|
@@ -130,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
128
|
version: '0'
|
131
129
|
requirements: []
|
132
130
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.
|
131
|
+
rubygems_version: 2.7.6
|
134
132
|
signing_key:
|
135
133
|
specification_version: 4
|
136
134
|
summary: A feature flipflopper for Rails web applications.
|