flipflop 2.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a26786a65478a45e78fe52884fd99b5a5a7e390bde362ce211832e57e3f66f9
4
- data.tar.gz: 6a75c2816df20f8ead6f24bcab02f4c2460fad380f6fbcd2ef1a79c3f9234fb9
3
+ metadata.gz: 5d280de2ff4eb5a0d3437866c2f6d08e6069564183d670cb437cc7741e7eeac3
4
+ data.tar.gz: e16c3770910db3cfc4a442e2094acf44426651f0456235992f8ea0166e53e6d9
5
5
  SHA512:
6
- metadata.gz: 47ecb8aeb5efb349c5a7366a559bd8eab2beb38dde0b19aa1202deae013072a3b158c1e77825241a372f2543287022d6a1d9e649755bf0130c3d823e6f604fdf
7
- data.tar.gz: fba2c4ca6ab07e14623d06470f733f54f1415198586792e5aee6e2f81e896a5c02973c0b2efb550d10e63d861cb0d617e23fbd6b87e4681c289322a408838f27
6
+ metadata.gz: b2ab3024b4106125d0f67a6333c5d2fc6b6e32d744e3fca9775894e9291cca0e2ecb3b74d6343598fc91c44cf0077b7496e79dade91fa27ce6418bb7fb3a534a
7
+ data.tar.gz: 6c55c846ba7759fa595485283a8d7d5eeaeb919e238a5752310b8bbfb6915f486cf4f750ab96acef3ec94da4295dab144fd6673e5802a0b26d4b3481d16654ba
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  ## 2.6.0
2
8
 
3
9
  * Failure to load strategies in test environments will result in a warning instead of an error. This should aid in running Rake tasks.
data/Gemfile CHANGED
@@ -19,6 +19,9 @@ group :test do
19
19
  else
20
20
  gem "sqlite3", "~> 1.3.6", platform: :ruby
21
21
  end
22
+ if version >= "5.2" || Gem::Version.new(RUBY_VERSION) > Gem::Version.new("2.4.4")
23
+ gem 'sassc-rails'
24
+ end
22
25
 
23
26
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby,
24
27
  github: "jruby/activerecord-jdbc-adapter"
data/README.md CHANGED
@@ -8,6 +8,7 @@ application functionality at run-time. It is originally based on
8
8
  * simple configuration
9
9
  * ease of use for developers
10
10
  * an improved dashboard
11
+ * manage features via console (using rake tasks)
11
12
  * thread safety
12
13
  * better database performance due to per-request caching, enabled by default
13
14
  * more strategies (Sequel, Redis, query strings, sessions, custom code)
@@ -32,9 +33,18 @@ Flipflop has a dashboard interface that's easy to understand and use.
32
33
 
33
34
  [<img src="https://raw.githubusercontent.com/voormedia/flipflop/screenshots/dashboard.png" alt="Dashboard">](https://raw.githubusercontent.com/voormedia/flipflop/screenshots/dashboard.png)
34
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
+
35
45
  ## Rails requirements
36
46
 
37
- This gem requires Rails 4, 5 or 6. Using an ORM layer is entirely optional.
47
+ This gem requires Rails 4, 5, 6 or 7. Using an ORM layer is entirely optional.
38
48
 
39
49
  ## Installation
40
50
 
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../javascripts .js
3
+ //= link_directory ../stylesheets .css
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <title><%= yield :title -%></title>
5
- <style><%= render partial: "flipflop/stylesheets/flipflop.css" %></style>
5
+ <style><%= render partial: "flipflop/stylesheets/flipflop" %></style>
6
6
  </head>
7
7
  <body><%= yield -%></body>
8
8
  </html>
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) }
@@ -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
- ActionController::Base.send(:include, interceptor)
41
- ActionController::API.send(:include, interceptor) if defined?(ActionController::API)
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(&Proc.new)
65
+ klass.to_prepare(&block)
55
66
  end
56
67
  end
57
68
  end
@@ -28,18 +28,17 @@ module Flipflop
28
28
  private :new
29
29
  end
30
30
 
31
- attr_reader :raise_strategy_errors
31
+ attr_accessor :raise_strategy_errors
32
32
 
33
33
  def initialize
34
- @raise_strategy_errors = (ENV["RACK_ENV"] || ENV["RAILS_ENV"]) != "test"
35
34
  @features = {}
36
35
  @strategies = {}
37
36
  end
38
37
 
39
- def configure
38
+ def configure(&block)
40
39
  Module.new do
41
40
  extend Configurable
42
- instance_exec(&Proc.new)
41
+ instance_exec(&block)
43
42
  end
44
43
  self
45
44
  end
@@ -1,3 +1,3 @@
1
1
  module Flipflop
2
- VERSION = "2.6.0"
2
+ VERSION = "2.7.0"
3
3
  end
@@ -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
@@ -181,8 +181,9 @@ class TestApp
181
181
 
182
182
  if defined?(ActiveRecord::Migrator.migrate)
183
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
184
186
  else
185
- # Rails 5.2+
186
187
  ActiveRecord::MigrationContext.new(Rails.application.paths["db/migrate"]).migrate
187
188
  end
188
189
  end
@@ -108,29 +108,19 @@ describe Flipflop::Configurable do
108
108
  Flipflop::FeatureSet.current.strategies.map(&:description)
109
109
  end
110
110
 
111
- it "should raise error when strategy fails to load" do
112
- begin
113
- rack_env, ENV["RACK_ENV"] = ENV["RACK_ENV"], nil
114
- rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil
115
- assert_raises "Oops" do
116
- subject.strategy(FailingStrategy)
117
- end
118
- ensure
119
- ENV["RACK_ENV"] = rack_env
120
- ENV["RAILS_ENV"] = rails_env
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)
121
116
  end
122
117
  end
123
118
 
124
- it "should not raise error when strategy fails to load in test mode" do
125
- begin
126
- rack_env, ENV["RACK_ENV"] = ENV["RACK_ENV"], "test"
127
- rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "test"
128
- assert_equal "WARNING: Unable to load Flipflop strategy FailingStrategy: Oops\n",
129
- capture_stderr { subject.strategy(FailingStrategy) }
130
- ensure
131
- ENV["RACK_ENV"] = rack_env
132
- ENV["RAILS_ENV"] = rails_env
133
- end
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) }
134
124
  end
135
125
  end
136
126
  end
@@ -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 -0000; HttpOnly",
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,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.6.0
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: 2019-05-03 00:00:00.000000000 Z
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,10 +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
63
  - app/views/flipflop/features/index.html.erb
49
- - app/views/flipflop/stylesheets/_flipflop.css
64
+ - app/views/flipflop/stylesheets/_flipflop.html.erb
50
65
  - app/views/layouts/flipflop.html.erb
51
66
  - config/features.rb
52
67
  - config/locales/en.yml
@@ -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
@@ -109,11 +126,12 @@ files:
109
126
  - test/unit/strategies/session_strategy_test.rb
110
127
  - test/unit/strategies/test_strategy_test.rb
111
128
  - test/unit/strategies_controller_test.rb
129
+ - test/unit/tasks/support/methods_test.rb
112
130
  homepage: https://github.com/voormedia/flipflop
113
131
  licenses:
114
132
  - MIT
115
133
  metadata: {}
116
- post_install_message:
134
+ post_install_message:
117
135
  rdoc_options: []
118
136
  require_paths:
119
137
  - lib
@@ -128,9 +146,35 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
146
  - !ruby/object:Gem::Version
129
147
  version: '0'
130
148
  requirements: []
131
- rubyforge_project:
132
- rubygems_version: 2.7.7
133
- signing_key:
149
+ rubygems_version: 3.1.6
150
+ signing_key:
134
151
  specification_version: 4
135
152
  summary: A feature flipflopper for Rails web applications.
136
- 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