flipflop 2.4.0 → 2.7.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 +4 -4
- data/.travis.yml +6 -6
- data/CHANGES.md +15 -0
- data/Gemfile +10 -1
- data/LICENSE +1 -1
- data/README.md +22 -6
- data/app/assets/config/manifest.js +3 -0
- data/app/controllers/flipflop/features_controller.rb +6 -1
- data/app/views/flipflop/stylesheets/{_flipflop.css → _flipflop.html.erb} +0 -0
- data/app/views/layouts/flipflop.html.erb +1 -1
- data/flipflop.gemspec +1 -0
- data/lib/flipflop/configurable.rb +6 -0
- data/lib/flipflop/engine.rb +15 -4
- data/lib/flipflop/facade.rb +2 -0
- data/lib/flipflop/feature_set.rb +4 -2
- data/lib/flipflop/strategies/active_record_strategy.rb +8 -1
- data/lib/flipflop/strategies/sequel_strategy.rb +57 -0
- data/lib/flipflop/version.rb +1 -1
- data/lib/flipflop.rb +1 -0
- data/lib/generators/flipflop/features/templates/features.rb +3 -1
- data/lib/generators/flipflop/install/install_generator.rb +4 -0
- data/lib/tasks/flipflop.rake +31 -0
- data/lib/tasks/support/methods.rb +71 -0
- data/test/test_helper.rb +37 -8
- data/test/unit/configurable_test.rb +22 -0
- data/test/unit/strategies/active_record_strategy_test.rb +16 -16
- data/test/unit/strategies/cookie_strategy_test.rb +1 -1
- data/test/unit/strategies/sequel_strategy_test.rb +165 -0
- data/test/unit/tasks/support/methods_test.rb +68 -0
- metadata +55 -10
- data/app/models/flipflop/feature.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d280de2ff4eb5a0d3437866c2f6d08e6069564183d670cb437cc7741e7eeac3
|
4
|
+
data.tar.gz: e16c3770910db3cfc4a442e2094acf44426651f0456235992f8ea0166e53e6d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2ab3024b4106125d0f67a6333c5d2fc6b6e32d744e3fca9775894e9291cca0e2ecb3b74d6343598fc91c44cf0077b7496e79dade91fa27ce6418bb7fb3a534a
|
7
|
+
data.tar.gz: 6c55c846ba7759fa595485283a8d7d5eeaeb919e238a5752310b8bbfb6915f486cf4f750ab96acef3ec94da4295dab144fd6673e5802a0b26d4b3481d16654ba
|
data/.travis.yml
CHANGED
@@ -2,7 +2,7 @@ language: ruby
|
|
2
2
|
cache: bundler
|
3
3
|
rvm:
|
4
4
|
- 2.5.0
|
5
|
-
- 2.4.
|
5
|
+
- 2.4.4
|
6
6
|
- 2.3.5
|
7
7
|
- jruby-9.1.13.0
|
8
8
|
- ruby-head
|
@@ -18,16 +18,12 @@ env:
|
|
18
18
|
- RAILS_VERSION=5.0 RAILS_API_ONLY=1
|
19
19
|
matrix:
|
20
20
|
include:
|
21
|
-
- rvm: 2.3.5
|
22
|
-
env: RAILS_VERSION=4.2
|
23
21
|
- rvm: 2.2.8
|
24
22
|
env: RAILS_VERSION=4.2
|
25
23
|
- rvm: 2.1.10
|
26
24
|
env: RAILS_VERSION=4.2
|
27
25
|
- rvm: 2.0.0
|
28
26
|
env: RAILS_VERSION=4.2
|
29
|
-
- rvm: 2.3.5
|
30
|
-
env: RAILS_VERSION=4.1
|
31
27
|
- rvm: 2.2.8
|
32
28
|
env: RAILS_VERSION=4.1
|
33
29
|
- rvm: 2.1.10
|
@@ -35,6 +31,10 @@ matrix:
|
|
35
31
|
- rvm: 2.0.0
|
36
32
|
env: RAILS_VERSION=4.1
|
37
33
|
exclude:
|
34
|
+
- rvm: 2.4.4
|
35
|
+
env: RAILS_VERSION=master
|
36
|
+
- rvm: 2.4.4
|
37
|
+
env: RAILS_VERSION=master RAILS_API_ONLY=1
|
38
38
|
- rvm: 2.3.5
|
39
39
|
env: RAILS_VERSION=master
|
40
40
|
- rvm: 2.3.5
|
@@ -52,4 +52,4 @@ deploy:
|
|
52
52
|
on:
|
53
53
|
tags: true
|
54
54
|
repo: voormedia/flipflop
|
55
|
-
ruby: 2.
|
55
|
+
ruby: 2.5.0
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## 2.7.0
|
2
|
+
|
3
|
+
* Removing .css extension from render partial and adding it to html handler to correctly render the erb file.
|
4
|
+
* Fixes Rails 6 & 7 deprecation: `Render file with extension is deprecated`
|
5
|
+
* Ruby 3.x compatible (Credits: https://github.com/voormedia/flipflop/pull/36)
|
6
|
+
|
7
|
+
## 2.6.0
|
8
|
+
|
9
|
+
* Failure to load strategies in test environments will result in a warning instead of an error. This should aid in running Rake tasks.
|
10
|
+
|
11
|
+
## 2.5.0
|
12
|
+
|
13
|
+
* Add Sequel strategy. ActiveRecord/Sequel can be used side by side.
|
14
|
+
* Rails 6 compatibility.
|
15
|
+
|
1
16
|
## 2.4.0
|
2
17
|
|
3
18
|
* Add location of feature definition.
|
data/Gemfile
CHANGED
@@ -13,7 +13,16 @@ group :test do
|
|
13
13
|
gem "bootstrap", "= 4.0.0.alpha6", require: false
|
14
14
|
|
15
15
|
gem "fakeredis", require: false
|
16
|
-
|
16
|
+
|
17
|
+
if version == "master" || version >= "6"
|
18
|
+
gem "sqlite3", "~> 1.4.0", platform: :ruby
|
19
|
+
else
|
20
|
+
gem "sqlite3", "~> 1.3.6", platform: :ruby
|
21
|
+
end
|
22
|
+
if version >= "5.2" || Gem::Version.new(RUBY_VERSION) > Gem::Version.new("2.4.4")
|
23
|
+
gem 'sassc-rails'
|
24
|
+
end
|
25
|
+
|
17
26
|
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby,
|
18
27
|
github: "jruby/activerecord-jdbc-adapter"
|
19
28
|
|
data/LICENSE
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
The MIT License
|
2
2
|
|
3
3
|
Copyright (c) 2011-2013 Learnable Pty Ltd
|
4
|
-
Copyright (c) 2016-
|
4
|
+
Copyright (c) 2016-2019 Voormedia
|
5
5
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -4,11 +4,14 @@
|
|
4
4
|
|
5
5
|
**Flipflop** provides a declarative, layered way of enabling and disabling
|
6
6
|
application functionality at run-time. It is originally based on
|
7
|
-
[Flip](https://github.com/pda/flip).
|
7
|
+
[Flip](https://github.com/pda/flip). **Flipflop** has the following features:
|
8
|
+
* simple configuration
|
9
|
+
* ease of use for developers
|
8
10
|
* an improved dashboard
|
11
|
+
* manage features via console (using rake tasks)
|
9
12
|
* thread safety
|
10
13
|
* better database performance due to per-request caching, enabled by default
|
11
|
-
* more strategies (Redis, query strings, sessions, custom code)
|
14
|
+
* more strategies (Sequel, Redis, query strings, sessions, custom code)
|
12
15
|
* more strategy options (cookie options, strategy names and descriptions, custom database models)
|
13
16
|
* the ability to use the same strategy twice, with different options
|
14
17
|
* configuration in a fixed location (`config/features.rb`) that is usable even if you don't use the database strategy
|
@@ -21,7 +24,7 @@ application functionality at run-time. It is originally based on
|
|
21
24
|
You can configure strategy layers that will evaluate if a feature is currently
|
22
25
|
enabled or disabled. Available strategies are:
|
23
26
|
* a per-feature default setting
|
24
|
-
* database (with Active Record or Redis), to flipflop features site-wide for all users
|
27
|
+
* database (with Active Record, Sequel, or Redis), to flipflop features site-wide for all users
|
25
28
|
* cookie or session, to flipflop features for single users
|
26
29
|
* query string parameters, to flipflop features occasionally (in development mode for example)
|
27
30
|
* custom strategy code
|
@@ -30,6 +33,19 @@ Flipflop has a dashboard interface that's easy to understand and use.
|
|
30
33
|
|
31
34
|
[<img src="https://raw.githubusercontent.com/voormedia/flipflop/screenshots/dashboard.png" alt="Dashboard">](https://raw.githubusercontent.com/voormedia/flipflop/screenshots/dashboard.png)
|
32
35
|
|
36
|
+
If you prefer, you can use the included rake tasks to enable or disable features.
|
37
|
+
|
38
|
+
```
|
39
|
+
rake flipflop:features # Shows features table
|
40
|
+
rake flipflop:turn_on[feature,strategy] # Enables a feature with the specified strategy
|
41
|
+
rake flipflop:turn_off[feature,strategy] # Disables a feature with the specified strategy
|
42
|
+
rake flipflop:clear[feature,strategy] # Clears a feature with the specified strategy
|
43
|
+
```
|
44
|
+
|
45
|
+
## Rails requirements
|
46
|
+
|
47
|
+
This gem requires Rails 4, 5, 6 or 7. Using an ORM layer is entirely optional.
|
48
|
+
|
33
49
|
## Installation
|
34
50
|
|
35
51
|
Add the gem to your `Gemfile`:
|
@@ -58,7 +74,7 @@ Features and strategies are declared in `config/features.rb`:
|
|
58
74
|
Flipflop.configure do
|
59
75
|
# Strategies will be used in the order listed here.
|
60
76
|
strategy :cookie
|
61
|
-
strategy :active_record
|
77
|
+
strategy :active_record # or :sequel, :redis
|
62
78
|
strategy :default
|
63
79
|
|
64
80
|
# Basic feature declaration:
|
@@ -86,8 +102,8 @@ Feature definitions support these options:
|
|
86
102
|
## Strategies
|
87
103
|
|
88
104
|
The following strategies are provided:
|
89
|
-
* `:active_record` – Save feature settings in the database.
|
90
|
-
* `:class` – Provide the feature model. `Flipflop::Feature` by default (which uses the table `flipflop_features`).
|
105
|
+
* `:active_record`/`:sequel` – Save feature settings in the database.
|
106
|
+
* `:class` – Provide the feature model. `Flipflop::Feature` by default (which is defined automatically and uses the table `flipflop_features`). The `ActiveRecord` version honors `default_scope` when features are resolved or switched on/off.
|
91
107
|
* `:cookie` – Save feature settings in browser cookies for the current user.
|
92
108
|
* `:prefix` – String prefix for all cookie names. Defaults to no prefix.
|
93
109
|
* `:path` – The path for which the cookies apply. Defaults to the root of the application.
|
@@ -25,7 +25,12 @@ module Flipflop
|
|
25
25
|
@strategies = @feature_set.strategies.reject(&:hidden?)
|
26
26
|
@grouped_features = @feature_set.features.group_by(&:group)
|
27
27
|
|
28
|
-
|
28
|
+
app_class = Rails.application.class
|
29
|
+
application_name = app_class.respond_to?(:module_parent_name) ?
|
30
|
+
app_class.module_parent_name :
|
31
|
+
app_class.parent_name
|
32
|
+
|
33
|
+
@application_name = application_name.underscore.titleize
|
29
34
|
end
|
30
35
|
|
31
36
|
def grouped?
|
File without changes
|
data/flipflop.gemspec
CHANGED
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.license = "MIT"
|
15
15
|
|
16
16
|
s.add_dependency("activesupport", ">= 4.0")
|
17
|
+
s.add_dependency('terminal-table', '>= 1.8')
|
17
18
|
s.files = `git ls-files`.split("\n")
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -32,6 +32,12 @@ module Flipflop
|
|
32
32
|
end
|
33
33
|
|
34
34
|
FeatureSet.current.use(strategy)
|
35
|
+
rescue StandardError => err
|
36
|
+
if FeatureSet.current.raise_strategy_errors
|
37
|
+
raise err
|
38
|
+
else
|
39
|
+
warn "WARNING: Unable to load Flipflop strategy #{strategy}: #{err}"
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
data/lib/flipflop/engine.rb
CHANGED
@@ -14,6 +14,12 @@ module Flipflop
|
|
14
14
|
|
15
15
|
config.flipflop = ActiveSupport::OrderedOptions.new
|
16
16
|
|
17
|
+
initializer "flipflop.config" do |app|
|
18
|
+
raise_errors = config.flipflop.raise_strategy_errors
|
19
|
+
raise_errors = (ENV["RACK_ENV"] || ENV["RAILS_ENV"]) != "test" if raise_errors.nil?
|
20
|
+
FeatureSet.current.raise_strategy_errors = raise_errors
|
21
|
+
end
|
22
|
+
|
17
23
|
initializer "flipflop.features_path" do |app|
|
18
24
|
FeatureLoader.current.append(app)
|
19
25
|
end
|
@@ -37,8 +43,13 @@ module Flipflop
|
|
37
43
|
|
38
44
|
initializer "flipflop.request_interceptor" do |app|
|
39
45
|
interceptor = Strategies::AbstractStrategy::RequestInterceptor
|
40
|
-
|
41
|
-
|
46
|
+
ActiveSupport.on_load(:action_controller_base) do
|
47
|
+
ActionController::Base.send(:include, interceptor)
|
48
|
+
end
|
49
|
+
|
50
|
+
ActiveSupport.on_load(:action_controller_api) do
|
51
|
+
ActionController::API.send(:include, interceptor)
|
52
|
+
end
|
42
53
|
end
|
43
54
|
|
44
55
|
def run_tasks_blocks(app)
|
@@ -49,9 +60,9 @@ module Flipflop
|
|
49
60
|
|
50
61
|
private
|
51
62
|
|
52
|
-
def to_prepare
|
63
|
+
def to_prepare(&block)
|
53
64
|
klass = defined?(ActiveSupport::Reloader) ? ActiveSupport::Reloader : ActionDispatch::Reloader
|
54
|
-
klass.to_prepare(&
|
65
|
+
klass.to_prepare(&block)
|
55
66
|
end
|
56
67
|
end
|
57
68
|
end
|
data/lib/flipflop/facade.rb
CHANGED
data/lib/flipflop/feature_set.rb
CHANGED
@@ -28,15 +28,17 @@ module Flipflop
|
|
28
28
|
private :new
|
29
29
|
end
|
30
30
|
|
31
|
+
attr_accessor :raise_strategy_errors
|
32
|
+
|
31
33
|
def initialize
|
32
34
|
@features = {}
|
33
35
|
@strategies = {}
|
34
36
|
end
|
35
37
|
|
36
|
-
def configure
|
38
|
+
def configure(&block)
|
37
39
|
Module.new do
|
38
40
|
extend Configurable
|
39
|
-
instance_exec(&
|
41
|
+
instance_exec(&block)
|
40
42
|
end
|
41
43
|
self
|
42
44
|
end
|
@@ -5,10 +5,17 @@ module Flipflop
|
|
5
5
|
def default_description
|
6
6
|
"Stores features in database. Applies to all users."
|
7
7
|
end
|
8
|
+
|
9
|
+
def define_feature_class
|
10
|
+
return Flipflop::Feature if defined?(Flipflop::Feature)
|
11
|
+
|
12
|
+
model = Class.new(ActiveRecord::Base)
|
13
|
+
Flipflop.const_set(:Feature, model)
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
17
|
def initialize(**options)
|
11
|
-
@class = options.delete(:class) ||
|
18
|
+
@class = options.delete(:class) || self.class.define_feature_class
|
12
19
|
if !@class.kind_of?(Class)
|
13
20
|
@class = ActiveSupport::Inflector.constantize(@class.to_s)
|
14
21
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Flipflop
|
2
|
+
module Strategies
|
3
|
+
class SequelStrategy < AbstractStrategy
|
4
|
+
class << self
|
5
|
+
def default_description
|
6
|
+
"Stores features in database. Applies to all users."
|
7
|
+
end
|
8
|
+
|
9
|
+
def define_feature_class
|
10
|
+
return Flipflop::Feature if defined?(Flipflop::Feature)
|
11
|
+
|
12
|
+
model = Class.new(Sequel::Model(:flipflop_features))
|
13
|
+
model.plugin(:timestamps, force: true, update_on_create: true)
|
14
|
+
model.raise_on_save_failure = true
|
15
|
+
|
16
|
+
Flipflop.const_set(:Feature, model)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(**options)
|
21
|
+
@class = options.delete(:class) || self.class.define_feature_class
|
22
|
+
if !@class.kind_of?(Class)
|
23
|
+
@class = ActiveSupport::Inflector.constantize(@class.to_s)
|
24
|
+
end
|
25
|
+
super(**options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def switchable?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def enabled?(feature)
|
33
|
+
find(feature).try(:enabled?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def switch!(feature, enabled)
|
37
|
+
record = find_or_new(feature)
|
38
|
+
record.enabled = enabled
|
39
|
+
record.save
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear!(feature)
|
43
|
+
find(feature).try(:destroy)
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def find_or_new(feature)
|
49
|
+
find(feature) || @class.new(key: feature.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
def find(feature)
|
53
|
+
@class.where(key: feature.to_s).first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/flipflop/version.rb
CHANGED
data/lib/flipflop.rb
CHANGED
@@ -22,6 +22,7 @@ require "flipflop/strategies/lambda_strategy"
|
|
22
22
|
require "flipflop/strategies/query_string_strategy"
|
23
23
|
require "flipflop/strategies/redis_strategy"
|
24
24
|
require "flipflop/strategies/session_strategy"
|
25
|
+
require "flipflop/strategies/sequel_strategy"
|
25
26
|
require "flipflop/strategies/test_strategy"
|
26
27
|
|
27
28
|
require "flipflop/engine" if defined?(Rails)
|
@@ -24,6 +24,10 @@ class Flipflop::InstallGenerator < Rails::Generators::Base
|
|
24
24
|
# Before filter for Flipflop dashboard. Replace with a lambda or method name
|
25
25
|
# defined in ApplicationController to implement access control.
|
26
26
|
config.flipflop.dashboard_access_filter = #{access_filter}
|
27
|
+
|
28
|
+
# By default, when set to `nil`, strategy loading errors are suppressed in test
|
29
|
+
# mode. Set to `true` to always raise errors, or `false` to always warn.
|
30
|
+
config.flipflop.raise_strategy_errors = nil
|
27
31
|
RUBY
|
28
32
|
end
|
29
33
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'support/methods'
|
2
|
+
|
3
|
+
namespace :flipflop do
|
4
|
+
# Encapsulates support methods to prevent name collision with global
|
5
|
+
# rake namespace
|
6
|
+
m = Object.new
|
7
|
+
m.extend Flipflop::Rake::SupportMethods
|
8
|
+
|
9
|
+
desc 'Enables a feature with the specified strategy.'
|
10
|
+
task :turn_on, %i[feature strategy] => :environment do |_task, args|
|
11
|
+
m.switch_feature! args[:feature], args[:strategy], true
|
12
|
+
puts "Feature :#{args[:feature]} enabled!"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Disables a feature with the specified strategy.'
|
16
|
+
task :turn_off, %i[feature strategy] => :environment do |_task, args|
|
17
|
+
m.switch_feature! args[:feature], args[:strategy], false
|
18
|
+
puts "Feature :#{args[:feature]} disabled!"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Clears a feature with the specified strategy.'
|
22
|
+
task :clear, %i[feature strategy] => :environment do |_task, args|
|
23
|
+
m.clear_feature! args[:feature], args[:strategy]
|
24
|
+
puts "Feature :#{args[:feature]} cleared!"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Shows features table'
|
28
|
+
task features: :environment do
|
29
|
+
puts m.build_features_table
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
module Flipflop
|
4
|
+
module Rake
|
5
|
+
module SupportMethods
|
6
|
+
def status_label(enabled)
|
7
|
+
enabled.nil? ? '' : (enabled ? 'ON' : 'OFF')
|
8
|
+
end
|
9
|
+
|
10
|
+
def switch_feature!(feature_name, strategy_name, value)
|
11
|
+
feature = find_feature_by_name(feature_name)
|
12
|
+
strategy = find_strategy_by_name(strategy_name)
|
13
|
+
|
14
|
+
if strategy.switchable?
|
15
|
+
strategy.switch!(feature.key, value)
|
16
|
+
else
|
17
|
+
raise "The :#{strategy_name} strategy doesn't support switching."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_feature!(feature_name, strategy_name)
|
22
|
+
feature = find_feature_by_name(feature_name)
|
23
|
+
strategy = find_strategy_by_name(strategy_name)
|
24
|
+
|
25
|
+
strategy.clear!(feature.key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_features_table
|
29
|
+
table_class.new(headings: table_header, rows: table_rows)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def features
|
35
|
+
Flipflop.feature_set.features
|
36
|
+
end
|
37
|
+
|
38
|
+
def strategies
|
39
|
+
Flipflop.feature_set.strategies
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_feature_by_name(name)
|
43
|
+
features.find { |f| f.name == name } || raise("Feature :#{name} is not defined.")
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_strategy_by_name(name)
|
47
|
+
strategies.find { |s| s.name == name } || raise("Strategy :#{name} is not available.")
|
48
|
+
end
|
49
|
+
|
50
|
+
def table_header
|
51
|
+
%w[feature description] + strategies.map(&:name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def table_class
|
55
|
+
Terminal::Table
|
56
|
+
end
|
57
|
+
|
58
|
+
def table_rows
|
59
|
+
features.inject([]) do |array, feature|
|
60
|
+
array << build_row_for(feature)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_row_for(feature)
|
65
|
+
strategies.inject([feature.name, feature.description]) do |row, strategy|
|
66
|
+
row << status_label(strategy.enabled?(feature.key))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -31,6 +31,20 @@ def create_request
|
|
31
31
|
request
|
32
32
|
end
|
33
33
|
|
34
|
+
def capture_stdout
|
35
|
+
stdout, $stdout = $stdout, StringIO.new
|
36
|
+
yield rescue nil
|
37
|
+
stdout, $stdout = $stdout, stdout
|
38
|
+
stdout.string
|
39
|
+
end
|
40
|
+
|
41
|
+
def capture_stderr
|
42
|
+
stderr, $stderr = $stderr, StringIO.new
|
43
|
+
yield rescue nil
|
44
|
+
stderr, $stderr = $stderr, stderr
|
45
|
+
stderr.string
|
46
|
+
end
|
47
|
+
|
34
48
|
def reload_constant(name)
|
35
49
|
ActiveSupport::Dependencies.remove_constant(name.to_s)
|
36
50
|
path = ActiveSupport::Dependencies.search_for_file(name.to_s.underscore).sub!(/\.rb\z/, "")
|
@@ -100,7 +114,11 @@ class TestApp
|
|
100
114
|
quiet: true,
|
101
115
|
api: ENV["RAILS_API_ONLY"].to_i.nonzero?,
|
102
116
|
skip_active_job: true,
|
117
|
+
skip_active_storage: true,
|
118
|
+
skip_action_cable: true,
|
119
|
+
skip_bootsnap: true,
|
103
120
|
skip_bundle: true,
|
121
|
+
skip_puma: true,
|
104
122
|
skip_gemfile: true,
|
105
123
|
skip_git: true,
|
106
124
|
skip_javascript: true,
|
@@ -128,11 +146,26 @@ class TestApp
|
|
128
146
|
require "rails"
|
129
147
|
require "flipflop/engine"
|
130
148
|
|
149
|
+
ActiveSupport::Dependencies.autoloaded_constants.clear
|
131
150
|
load File.expand_path("../../#{path}/config/application.rb", __FILE__)
|
132
151
|
load File.expand_path("../../#{path}/config/environments/test.rb", __FILE__)
|
133
152
|
Rails.application.config.cache_classes = false
|
134
153
|
Rails.application.config.action_view.raise_on_missing_translations = true
|
135
154
|
Rails.application.config.i18n.enforce_available_locales = false
|
155
|
+
Rails.application.config.autoloader = :classic # Disable Zeitwerk in Rails 6+
|
156
|
+
|
157
|
+
# Avoid Rails 6+ deprecation warning
|
158
|
+
if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) &&
|
159
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.respond_to?(:represent_boolean_as_integer=) &&
|
160
|
+
Rails.application.config.active_record.sqlite3.nil?
|
161
|
+
Rails.application.config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new
|
162
|
+
end
|
163
|
+
|
164
|
+
# Avoid Rails 6+ deprecation warning
|
165
|
+
if defined?(ActionView::Railtie::NULL_OPTION)
|
166
|
+
Rails.application.config.action_view.finalize_compiled_template_methods = ActionView::Railtie::NULL_OPTION
|
167
|
+
end
|
168
|
+
|
136
169
|
Rails.application.initialize!
|
137
170
|
|
138
171
|
I18n.locale = :en
|
@@ -143,18 +176,20 @@ class TestApp
|
|
143
176
|
def migrate!
|
144
177
|
ActiveRecord::Base.establish_connection
|
145
178
|
|
146
|
-
|
179
|
+
capture_stdout { ActiveRecord::Tasks::DatabaseTasks.create_current }
|
147
180
|
ActiveRecord::Migration.verbose = false
|
148
181
|
|
149
182
|
if defined?(ActiveRecord::Migrator.migrate)
|
150
183
|
ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"].to_a)
|
184
|
+
elsif ActiveRecord::Base.connection.respond_to?(:schema_migration)
|
185
|
+
ActiveRecord::MigrationContext.new(Rails.application.paths["db/migrate"], ActiveRecord::Base.connection.schema_migration).migrate
|
151
186
|
else
|
152
|
-
# Rails 5.2+
|
153
187
|
ActiveRecord::MigrationContext.new(Rails.application.paths["db/migrate"]).migrate
|
154
188
|
end
|
155
189
|
end
|
156
190
|
|
157
191
|
def unload!
|
192
|
+
ENV["RAILS_ENV"] = nil
|
158
193
|
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
159
194
|
Flipflop::FeatureLoader.instance_variable_set(:@current, nil)
|
160
195
|
|
@@ -173,12 +208,6 @@ class TestApp
|
|
173
208
|
|
174
209
|
private
|
175
210
|
|
176
|
-
def silence_stdout
|
177
|
-
stdout, $stdout = $stdout, StringIO.new
|
178
|
-
yield rescue nil
|
179
|
-
$stdout = stdout
|
180
|
-
end
|
181
|
-
|
182
211
|
def path
|
183
212
|
"tmp/" + name
|
184
213
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
require File.expand_path("../../test_helper", __FILE__)
|
2
2
|
|
3
|
+
class FailingStrategy < Flipflop::Strategies::AbstractStrategy
|
4
|
+
def initialize(*)
|
5
|
+
raise "Oops"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
3
10
|
describe Flipflop::Configurable do
|
4
11
|
subject do
|
5
12
|
Flipflop::FeatureSet.current.send(:initialize)
|
@@ -100,5 +107,20 @@ describe Flipflop::Configurable do
|
|
100
107
|
assert_equal ["awesome"],
|
101
108
|
Flipflop::FeatureSet.current.strategies.map(&:description)
|
102
109
|
end
|
110
|
+
|
111
|
+
it "should raise error when strategy fails to load if unsuppressed" do
|
112
|
+
subject
|
113
|
+
Flipflop::FeatureSet.current.raise_strategy_errors = true
|
114
|
+
assert_raises "Oops" do
|
115
|
+
subject.strategy(FailingStrategy)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should not raise error when strategy fails to load if suppressed" do
|
120
|
+
subject
|
121
|
+
Flipflop::FeatureSet.current.raise_strategy_errors = false
|
122
|
+
assert_equal "WARNING: Unable to load Flipflop strategy FailingStrategy: Oops\n",
|
123
|
+
capture_stderr { subject.strategy(FailingStrategy) }
|
124
|
+
end
|
103
125
|
end
|
104
126
|
end
|
@@ -6,7 +6,7 @@ class ResultSet
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def first_or_initialize
|
9
|
-
@results.first or
|
9
|
+
@results.first or MyAr::Feature.new(@key, false)
|
10
10
|
end
|
11
11
|
|
12
12
|
def first
|
@@ -14,7 +14,7 @@ class ResultSet
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
module
|
17
|
+
module MyAr
|
18
18
|
class Feature < Struct.new(:key, :enabled)
|
19
19
|
class << self
|
20
20
|
attr_accessor :results
|
@@ -27,11 +27,11 @@ module My
|
|
27
27
|
alias_method :enabled?, :enabled
|
28
28
|
|
29
29
|
def destroy
|
30
|
-
|
30
|
+
MyAr::Feature.results[key] = ResultSet.new(key)
|
31
31
|
end
|
32
32
|
|
33
33
|
def save!
|
34
|
-
|
34
|
+
MyAr::Feature.results[key] = ResultSet.new(key, [self])
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -39,7 +39,7 @@ end
|
|
39
39
|
describe Flipflop::Strategies::ActiveRecordStrategy do
|
40
40
|
describe "with defaults" do
|
41
41
|
subject do
|
42
|
-
Flipflop::Strategies::ActiveRecordStrategy.new(class:
|
42
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: MyAr::Feature).freeze
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should have default name" do
|
@@ -65,8 +65,8 @@ describe Flipflop::Strategies::ActiveRecordStrategy do
|
|
65
65
|
|
66
66
|
describe "with enabled feature" do
|
67
67
|
before do
|
68
|
-
|
69
|
-
one: ResultSet.new(:one, [
|
68
|
+
MyAr::Feature.results = {
|
69
|
+
one: ResultSet.new(:one, [MyAr::Feature.new(:one, true)]),
|
70
70
|
}
|
71
71
|
end
|
72
72
|
|
@@ -87,8 +87,8 @@ describe Flipflop::Strategies::ActiveRecordStrategy do
|
|
87
87
|
|
88
88
|
describe "with disabled feature" do
|
89
89
|
before do
|
90
|
-
|
91
|
-
two: ResultSet.new(:two, [
|
90
|
+
MyAr::Feature.results = {
|
91
|
+
two: ResultSet.new(:two, [MyAr::Feature.new(:two, false)]),
|
92
92
|
}
|
93
93
|
end
|
94
94
|
|
@@ -109,7 +109,7 @@ describe Flipflop::Strategies::ActiveRecordStrategy do
|
|
109
109
|
|
110
110
|
describe "with unsaved feature" do
|
111
111
|
before do
|
112
|
-
|
112
|
+
MyAr::Feature.results = {
|
113
113
|
three: ResultSet.new(:three),
|
114
114
|
}
|
115
115
|
end
|
@@ -127,12 +127,12 @@ describe Flipflop::Strategies::ActiveRecordStrategy do
|
|
127
127
|
|
128
128
|
describe "with string class name" do
|
129
129
|
subject do
|
130
|
-
Flipflop::Strategies::ActiveRecordStrategy.new(class: "
|
130
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: "MyAr::Feature").freeze
|
131
131
|
end
|
132
132
|
|
133
133
|
before do
|
134
|
-
|
135
|
-
one: ResultSet.new(:one, [
|
134
|
+
MyAr::Feature.results = {
|
135
|
+
one: ResultSet.new(:one, [MyAr::Feature.new(:one, true)]),
|
136
136
|
}
|
137
137
|
end
|
138
138
|
|
@@ -144,12 +144,12 @@ describe Flipflop::Strategies::ActiveRecordStrategy do
|
|
144
144
|
|
145
145
|
describe "with symbol class name" do
|
146
146
|
subject do
|
147
|
-
Flipflop::Strategies::ActiveRecordStrategy.new(class: :"
|
147
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: :"MyAr::Feature").freeze
|
148
148
|
end
|
149
149
|
|
150
150
|
before do
|
151
|
-
|
152
|
-
one: ResultSet.new(:one, [
|
151
|
+
MyAr::Feature.results = {
|
152
|
+
one: ResultSet.new(:one, [MyAr::Feature.new(:one, true)]),
|
153
153
|
}
|
154
154
|
end
|
155
155
|
|
@@ -112,7 +112,7 @@ describe Flipflop::Strategies::CookieStrategy do
|
|
112
112
|
subject.switch!(:one, true)
|
113
113
|
subject.clear!(:one)
|
114
114
|
subject.send(:request).cookie_jar.write(headers = {})
|
115
|
-
assert_equal "my_cookie_one=; domain=.example.com; path=/foo; max-age=0; expires=Thu, 01 Jan 1970 00:00:00
|
115
|
+
assert_equal "my_cookie_one=; domain=.example.com; path=/foo; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly",
|
116
116
|
headers["Set-Cookie"]
|
117
117
|
end
|
118
118
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require File.expand_path("../../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class ResultSet
|
4
|
+
def initialize(key, results = [])
|
5
|
+
@key, @results = key, results
|
6
|
+
end
|
7
|
+
|
8
|
+
def first
|
9
|
+
@results.first
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module MySq
|
14
|
+
class Feature < Struct.new(:key, :enabled)
|
15
|
+
class << self
|
16
|
+
attr_accessor :results
|
17
|
+
|
18
|
+
def where(conditions)
|
19
|
+
results[conditions[:key].to_sym]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :enabled?, :enabled
|
24
|
+
|
25
|
+
def initialize(key, value = false)
|
26
|
+
if key.kind_of?(Hash)
|
27
|
+
key.each { |k, v| self[k] = v.to_sym }
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy
|
34
|
+
MySq::Feature.results[key] = ResultSet.new(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
MySq::Feature.results[key] = ResultSet.new(key, [self])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Flipflop::Strategies::SequelStrategy do
|
44
|
+
describe "with defaults" do
|
45
|
+
subject do
|
46
|
+
Flipflop::Strategies::SequelStrategy.new(class: MySq::Feature).freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should have default name" do
|
50
|
+
assert_equal "sequel", subject.name
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have title derived from name" do
|
54
|
+
assert_equal "Sequel", subject.title
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have default description" do
|
58
|
+
assert_equal "Stores features in database. Applies to all users.",
|
59
|
+
subject.description
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be switchable" do
|
63
|
+
assert_equal true, subject.switchable?
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should have unique key" do
|
67
|
+
assert_match /^\w+$/, subject.key
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "with enabled feature" do
|
71
|
+
before do
|
72
|
+
MySq::Feature.results = {
|
73
|
+
one: ResultSet.new(:one, [MySq::Feature.new(:one, true)]),
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have feature enabled" do
|
78
|
+
assert_equal true, subject.enabled?(:one)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be able to switch feature off" do
|
82
|
+
subject.switch!(:one, false)
|
83
|
+
assert_equal false, subject.enabled?(:one)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should be able to clear feature" do
|
87
|
+
subject.clear!(:one)
|
88
|
+
assert_nil subject.enabled?(:one)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "with disabled feature" do
|
93
|
+
before do
|
94
|
+
MySq::Feature.results = {
|
95
|
+
two: ResultSet.new(:two, [MySq::Feature.new(:two, false)]),
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should not have feature enabled" do
|
100
|
+
assert_equal false, subject.enabled?(:two)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should be able to switch feature on" do
|
104
|
+
subject.switch!(:two, true)
|
105
|
+
assert_equal true, subject.enabled?(:two)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should be able to clear feature" do
|
109
|
+
subject.clear!(:two)
|
110
|
+
assert_nil subject.enabled?(:two)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "with unsaved feature" do
|
115
|
+
before do
|
116
|
+
MySq::Feature.results = {
|
117
|
+
three: ResultSet.new(:three),
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should not know feature" do
|
122
|
+
assert_nil subject.enabled?(:three)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should be able to switch feature on" do
|
126
|
+
subject.switch!(:three, true)
|
127
|
+
assert_equal true, subject.enabled?(:three)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "with string class name" do
|
133
|
+
subject do
|
134
|
+
Flipflop::Strategies::SequelStrategy.new(class: "MySq::Feature").freeze
|
135
|
+
end
|
136
|
+
|
137
|
+
before do
|
138
|
+
MySq::Feature.results = {
|
139
|
+
one: ResultSet.new(:one, [MySq::Feature.new(:one, true)]),
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should be able to switch feature off" do
|
144
|
+
subject.switch!(:one, false)
|
145
|
+
assert_equal false, subject.enabled?(:one)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "with symbol class name" do
|
150
|
+
subject do
|
151
|
+
Flipflop::Strategies::SequelStrategy.new(class: :"MySq::Feature").freeze
|
152
|
+
end
|
153
|
+
|
154
|
+
before do
|
155
|
+
MySq::Feature.results = {
|
156
|
+
one: ResultSet.new(:one, [MySq::Feature.new(:one, true)]),
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should be able to switch feature off" do
|
161
|
+
subject.switch!(:one, false)
|
162
|
+
assert_equal false, subject.enabled?(:one)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path('../../../../test_helper', __FILE__)
|
2
|
+
require File.expand_path('../../../../../lib/tasks/support/methods', __FILE__)
|
3
|
+
|
4
|
+
describe Flipflop::Rake::SupportMethods do
|
5
|
+
subject do
|
6
|
+
object = Object.new
|
7
|
+
object.extend Flipflop::Rake::SupportMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#status_label' do
|
11
|
+
it 'returns the "enabled" label when its argument is "true"' do
|
12
|
+
assert_equal 'ON', subject.status_label(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the "disabled" label when its argument is "false"' do
|
16
|
+
assert_equal 'OFF', subject.status_label(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns the "unset" label when its argument is "nil"' do
|
20
|
+
assert_equal '', subject.status_label(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_feature_and_strategy(strategy = 'Test')
|
25
|
+
# Stubs finder methods to avoid going into FeatureSet code.
|
26
|
+
feature = Flipflop::FeatureDefinition.new(:world_domination)
|
27
|
+
subject.stub :find_feature_by_name, feature do
|
28
|
+
classname = "#{strategy}Strategy"
|
29
|
+
strategy = Flipflop::Strategies.const_get(classname).new(name: strategy)
|
30
|
+
subject.stub :find_strategy_by_name, strategy do
|
31
|
+
yield(strategy, feature) if block_given?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#switch_feature!' do
|
37
|
+
it 'enables a feature using a strategy' do
|
38
|
+
with_feature_and_strategy do |strategy, feature|
|
39
|
+
subject.switch_feature! 'world_domination', 'test', true
|
40
|
+
assert_equal true, strategy.enabled?(feature.key)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'disables a feature using a strategy' do
|
45
|
+
with_feature_and_strategy do |strategy, feature|
|
46
|
+
subject.switch_feature! 'world_domination', 'test', false
|
47
|
+
assert_equal false, strategy.enabled?(feature.key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'when the strategy is not switchable' do
|
52
|
+
it 'raises an error' do
|
53
|
+
with_feature_and_strategy 'Lambda' do |strategy, feature|
|
54
|
+
-> { subject.switch_feature!('world_domination', 'lambda', true) }.must_raise
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#clear_feature!' do
|
61
|
+
it 'clears a feature using a strategy' do
|
62
|
+
with_feature_and_strategy do |strategy, feature|
|
63
|
+
subject.clear_feature! 'world_domination', 'test'
|
64
|
+
assert_nil strategy.enabled?(feature.key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipflop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Annesley
|
8
8
|
- Rolf Timmermans
|
9
9
|
- Jippe Holwerda
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -26,6 +26,20 @@ dependencies:
|
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '4.0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: terminal-table
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '1.8'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.8'
|
29
43
|
description: Declarative API for specifying features, switchable in declaration, database
|
30
44
|
and cookies.
|
31
45
|
email:
|
@@ -43,11 +57,11 @@ files:
|
|
43
57
|
- LICENSE
|
44
58
|
- README.md
|
45
59
|
- Rakefile
|
60
|
+
- app/assets/config/manifest.js
|
46
61
|
- app/controllers/flipflop/features_controller.rb
|
47
62
|
- app/controllers/flipflop/strategies_controller.rb
|
48
|
-
- app/models/flipflop/feature.rb
|
49
63
|
- app/views/flipflop/features/index.html.erb
|
50
|
-
- app/views/flipflop/stylesheets/_flipflop.
|
64
|
+
- app/views/flipflop/stylesheets/_flipflop.html.erb
|
51
65
|
- app/views/layouts/flipflop.html.erb
|
52
66
|
- config/features.rb
|
53
67
|
- config/locales/en.yml
|
@@ -70,6 +84,7 @@ files:
|
|
70
84
|
- lib/flipflop/strategies/options_hasher.rb
|
71
85
|
- lib/flipflop/strategies/query_string_strategy.rb
|
72
86
|
- lib/flipflop/strategies/redis_strategy.rb
|
87
|
+
- lib/flipflop/strategies/sequel_strategy.rb
|
73
88
|
- lib/flipflop/strategies/session_strategy.rb
|
74
89
|
- lib/flipflop/strategies/test_strategy.rb
|
75
90
|
- lib/flipflop/version.rb
|
@@ -82,6 +97,8 @@ files:
|
|
82
97
|
- lib/generators/flipflop/migration/templates/create_features.rb
|
83
98
|
- lib/generators/flipflop/routes/USAGE
|
84
99
|
- lib/generators/flipflop/routes/routes_generator.rb
|
100
|
+
- lib/tasks/flipflop.rake
|
101
|
+
- lib/tasks/support/methods.rb
|
85
102
|
- src/stylesheets/_flipflop.scss
|
86
103
|
- test/integration/app_test.rb
|
87
104
|
- test/integration/dashboard_test.rb
|
@@ -105,14 +122,16 @@ files:
|
|
105
122
|
- test/unit/strategies/options_hasher_test.rb
|
106
123
|
- test/unit/strategies/query_string_strategy_test.rb
|
107
124
|
- test/unit/strategies/redis_strategy_test.rb
|
125
|
+
- test/unit/strategies/sequel_strategy_test.rb
|
108
126
|
- test/unit/strategies/session_strategy_test.rb
|
109
127
|
- test/unit/strategies/test_strategy_test.rb
|
110
128
|
- test/unit/strategies_controller_test.rb
|
129
|
+
- test/unit/tasks/support/methods_test.rb
|
111
130
|
homepage: https://github.com/voormedia/flipflop
|
112
131
|
licenses:
|
113
132
|
- MIT
|
114
133
|
metadata: {}
|
115
|
-
post_install_message:
|
134
|
+
post_install_message:
|
116
135
|
rdoc_options: []
|
117
136
|
require_paths:
|
118
137
|
- lib
|
@@ -127,9 +146,35 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
146
|
- !ruby/object:Gem::Version
|
128
147
|
version: '0'
|
129
148
|
requirements: []
|
130
|
-
|
131
|
-
|
132
|
-
signing_key:
|
149
|
+
rubygems_version: 3.1.6
|
150
|
+
signing_key:
|
133
151
|
specification_version: 4
|
134
152
|
summary: A feature flipflopper for Rails web applications.
|
135
|
-
test_files:
|
153
|
+
test_files:
|
154
|
+
- test/integration/app_test.rb
|
155
|
+
- test/integration/dashboard_test.rb
|
156
|
+
- test/templates/nl.yml
|
157
|
+
- test/templates/test_app_features.rb
|
158
|
+
- test/templates/test_engine.rb
|
159
|
+
- test/templates/test_engine_features.rb
|
160
|
+
- test/test_helper.rb
|
161
|
+
- test/unit/configurable_test.rb
|
162
|
+
- test/unit/feature_cache_test.rb
|
163
|
+
- test/unit/feature_definition_test.rb
|
164
|
+
- test/unit/feature_set_test.rb
|
165
|
+
- test/unit/flipflop_test.rb
|
166
|
+
- test/unit/group_definition_test.rb
|
167
|
+
- test/unit/strategies/abstract_strategy_request_test.rb
|
168
|
+
- test/unit/strategies/abstract_strategy_test.rb
|
169
|
+
- test/unit/strategies/active_record_strategy_test.rb
|
170
|
+
- test/unit/strategies/cookie_strategy_test.rb
|
171
|
+
- test/unit/strategies/default_strategy_test.rb
|
172
|
+
- test/unit/strategies/lambda_strategy_test.rb
|
173
|
+
- test/unit/strategies/options_hasher_test.rb
|
174
|
+
- test/unit/strategies/query_string_strategy_test.rb
|
175
|
+
- test/unit/strategies/redis_strategy_test.rb
|
176
|
+
- test/unit/strategies/sequel_strategy_test.rb
|
177
|
+
- test/unit/strategies/session_strategy_test.rb
|
178
|
+
- test/unit/strategies/test_strategy_test.rb
|
179
|
+
- test/unit/strategies_controller_test.rb
|
180
|
+
- test/unit/tasks/support/methods_test.rb
|