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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d991f9cd5e93e4a837c0e6835da5ae2339498bf74a26fc6e21cee4503793ca6
4
- data.tar.gz: 943ba8e6ea38bf2afbc45454890f8111a3b9029383018e5ddf3a76f8faac0732
3
+ metadata.gz: 5d280de2ff4eb5a0d3437866c2f6d08e6069564183d670cb437cc7741e7eeac3
4
+ data.tar.gz: e16c3770910db3cfc4a442e2094acf44426651f0456235992f8ea0166e53e6d9
5
5
  SHA512:
6
- metadata.gz: a81cbf3b37f9d91ec310bc971eec5c06b2d4b0581bd42191366503782bb8d95ef6b51fef33588bd28ec571a57ebc03f51cd1c5f9514ac8151f1a855e4072e085
7
- data.tar.gz: 30a184287b36b7a346cc72fb0bf4f2ed4cb59083461f3859d70d1ec9908c37b3c1b65e54dc3536ed472fed85f94465ea191c6d4eeb7b049679b4056cb3585b05
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.2
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.3.5
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
- gem "sqlite3", ">= 1.3", platform: :ruby
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-2017 Voormedia
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). Compared to the original gem **Flipflop** has:
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`). Honors `default_scope` when features are resolved or switched on/off.
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.
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../javascripts .js
3
+ //= link_directory ../stylesheets .css
@@ -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
- @application_name = Rails.application.class.parent_name.underscore.titleize
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?
@@ -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) }
@@ -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
@@ -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
@@ -1,3 +1,5 @@
1
+ require "forwardable"
2
+
1
3
  module Flipflop
2
4
  module Facade
3
5
  extend Forwardable
@@ -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(&Proc.new)
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) || ::Flipflop::Feature
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
@@ -1,3 +1,3 @@
1
1
  module Flipflop
2
- VERSION = "2.4.0"
2
+ VERSION = "2.7.0"
3
3
  end
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)
@@ -6,8 +6,10 @@ Flipflop.configure do
6
6
 
7
7
  # Other strategies:
8
8
  #
9
- # strategy :query_string
9
+ # strategy :sequel
10
10
  # strategy :redis
11
+ #
12
+ # strategy :query_string
11
13
  # strategy :session
12
14
  #
13
15
  # strategy :my_strategy do |feature|
@@ -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
- silence_stdout { ActiveRecord::Tasks::DatabaseTasks.create_current }
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 My::Feature.new(@key, false)
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 My
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
- My::Feature.results[key] = ResultSet.new(key)
30
+ MyAr::Feature.results[key] = ResultSet.new(key)
31
31
  end
32
32
 
33
33
  def save!
34
- My::Feature.results[key] = ResultSet.new(key, [self])
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: My::Feature).freeze
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
- My::Feature.results = {
69
- one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
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
- My::Feature.results = {
91
- two: ResultSet.new(:two, [My::Feature.new(:two, false)]),
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
- My::Feature.results = {
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: "My::Feature").freeze
130
+ Flipflop::Strategies::ActiveRecordStrategy.new(class: "MyAr::Feature").freeze
131
131
  end
132
132
 
133
133
  before do
134
- My::Feature.results = {
135
- one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
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: :"My::Feature").freeze
147
+ Flipflop::Strategies::ActiveRecordStrategy.new(class: :"MyAr::Feature").freeze
148
148
  end
149
149
 
150
150
  before do
151
- My::Feature.results = {
152
- one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
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 -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,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.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: 2018-04-26 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,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.css
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
- rubyforge_project:
131
- rubygems_version: 2.7.6
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
@@ -1,4 +0,0 @@
1
- if defined?(ActiveRecord)
2
- class Flipflop::Feature < ActiveRecord::Base
3
- end
4
- end