flipper 0.21.0.rc1 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
data/docs/api/README.md CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
23
23
  ```ruby
24
24
  # config/routes.rb
25
25
  YourRailsApp::Application.routes.draw do
26
- mount Flipper::Api.app(flipper) => '/flipper/api'
26
+ mount Flipper::Api.app(Flipper) => '/flipper/api'
27
27
  end
28
28
  ```
29
29
 
@@ -34,8 +34,8 @@ There can be more than one router in your application. Make sure if you choose a
34
34
  *bad:*
35
35
  ```ruby
36
36
  YourRailsApp::Application.routes.draw do
37
- mount Flipper::UI.app(flipper) => '/flipper'
38
- mount Flipper::Api.app(flipper) => '/flipper/api'
37
+ mount Flipper::UI.app(Flipper) => '/flipper'
38
+ mount Flipper::Api.app(Flipper) => '/flipper/api'
39
39
  end
40
40
  ```
41
41
 
@@ -44,8 +44,8 @@ In this case any requests to /flipper\* will be routed to Flipper::UI - includin
44
44
  *good:*
45
45
  ```ruby
46
46
  YourRailsApp::Application.routes.draw do
47
- mount Flipper::Api.app(flipper) => '/flipper/api'
48
- mount Flipper::UI.app(flipper) => '/flipper'
47
+ mount Flipper::Api.app(Flipper) => '/flipper/api'
48
+ mount Flipper::UI.app(Flipper) => '/flipper'
49
49
  end
50
50
  ````
51
51
  For more advanced mounting techniques and for suggestions on how to mount in a non-Rails application, it is recommend that you review the [`Flipper::UI` usage documentation](https://github.com/jnunemaker/flipper/blob/master/docs/ui/README.md#usage) as the same approaches apply to `Flipper::Api`.
data/docs/http/README.md CHANGED
@@ -8,17 +8,18 @@ Initialize the HTTP adapter with a configuration Hash.
8
8
  ```ruby
9
9
  require 'flipper/adapters/http'
10
10
 
11
- configuration = {
12
- url: 'http://app.com/mount-point', # required
13
- headers: { 'X-Custom-Header' => 'foo' },
14
- basic_auth_username: 'user123',
15
- basic_auth_password: 'password123'
16
- read_timeout: 5,
17
- open_timeout: 2,
18
- }
19
-
20
- adapter = Flipper::Adapters::Http.new(configuration)
21
- flipper = Flipper.new(adapter)
11
+ Flipper.configure do |config|
12
+ config.adapter do
13
+ Flipper::Adapters::Http.new({
14
+ url: 'http://app.com/mount-point', # required
15
+ headers: { 'X-Custom-Header' => 'foo' },
16
+ basic_auth_username: 'user123',
17
+ basic_auth_password: 'password123'
18
+ read_timeout: 5,
19
+ open_timeout: 2,
20
+ })
21
+ end
22
+ end
22
23
  ```
23
24
 
24
25
  **Required keys**:
@@ -5,17 +5,20 @@ A [read-only](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adap
5
5
  Use this adapter to wrap another adapter and raise an exception for any writes.
6
6
 
7
7
  Any attempted write raises `Flipper::Adapters::ReadOnly::WriteAttempted` with message `'write attempted while in read only mode'`
8
+
8
9
  ## Usage
10
+
9
11
  ```ruby
10
12
  # example wrapping memory adapter
11
13
  require 'flipper/adapters/read_only'
12
14
 
13
- memory_adapter = Flipper::Adapters::Memory.new
14
- read_only_adapter = Flipper::Adapters::ReadOnly.new(memory_adapter)
15
-
16
- flipper = Flipper.new(read_only_adapter)
15
+ Flipper.configure do |config|
16
+ config.adapter do
17
+ Flipper::Adapters::ReadOnly.new(Flipper::Adapters::Memory.new)
18
+ end
19
+ end
17
20
 
18
21
  # Enabling a feature
19
- > flipper[:dashboard_panel].enable
22
+ > Flipper[:dashboard_panel].enable
20
23
  => Flipper::Adapters::ReadOnly::WriteAttempted: write attempted while in read only mode
21
24
  ```
@@ -0,0 +1,19 @@
1
+ #
2
+ # Usage:
3
+ # # if you want it to not reload and be really fast
4
+ # bin/rackup examples/api/basic.ru -p 9999
5
+ #
6
+ # # if you want reloading
7
+ # bin/shotgun examples/api/basic.ru -p 9999
8
+ #
9
+ # http://localhost:9999/
10
+ #
11
+
12
+ require 'bundler/setup'
13
+ require "flipper/api"
14
+ require "flipper/adapters/pstore"
15
+
16
+ # You can uncomment this to get some default data:
17
+ # Flipper.enable :logging
18
+
19
+ run Flipper::Api.app
@@ -0,0 +1,37 @@
1
+ #
2
+ # Usage:
3
+ # # if you want it to not reload and be really fast
4
+ # bin/rackup examples/api/custom_memoized.ru -p 9999
5
+ #
6
+ # # if you want reloading
7
+ # bin/shotgun examples/api/custom_memoized.ru -p 9999
8
+ #
9
+ # http://localhost:9999/
10
+ #
11
+
12
+ require 'bundler/setup'
13
+ require "active_support/notifications"
14
+ require "flipper/api"
15
+ require "flipper/adapters/pstore"
16
+
17
+ adapter = Flipper::Adapters::Instrumented.new(
18
+ Flipper::Adapters::PStore.new,
19
+ instrumenter: ActiveSupport::Notifications,
20
+ )
21
+ flipper = Flipper.new(adapter)
22
+
23
+ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
24
+ name, start, finish, id, data = args
25
+ case name
26
+ when "adapter_operation.flipper"
27
+ p data[:adapter_name] => data[:operation]
28
+ end
29
+ })
30
+
31
+ # You can uncomment this to get some default data:
32
+ # flipper[:logging].enable_percentage_of_time 5
33
+
34
+ run Flipper::Api.app(flipper) { |builder|
35
+ builder.use Flipper::Middleware::SetupEnv, flipper
36
+ builder.use Flipper::Middleware::Memoizer, preload: true
37
+ }
@@ -0,0 +1,43 @@
1
+ #
2
+ # Usage:
3
+ # # if you want it to not reload and be really fast
4
+ # bin/rackup examples/api/memoized.ru -p 9999
5
+ #
6
+ # # if you want reloading
7
+ # bin/shotgun examples/api/memoized.ru -p 9999
8
+ #
9
+ # http://localhost:9999/
10
+ #
11
+
12
+ require 'bundler/setup'
13
+ require "active_support/notifications"
14
+ require "flipper/api"
15
+ require "flipper/adapters/pstore"
16
+
17
+ Flipper.configure do |config|
18
+ config.adapter {
19
+ Flipper::Adapters::Instrumented.new(
20
+ Flipper::Adapters::PStore.new,
21
+ instrumenter: ActiveSupport::Notifications,
22
+ )
23
+ }
24
+ end
25
+
26
+ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
27
+ name, start, finish, id, data = args
28
+ case name
29
+ when "adapter_operation.flipper"
30
+ p data[:adapter_name] => data[:operation]
31
+ end
32
+ })
33
+
34
+ Flipper.register(:admins) { |actor|
35
+ actor.respond_to?(:admin?) && actor.admin?
36
+ }
37
+
38
+ # You can uncomment this to get some default data:
39
+ # Flipper.enable :logging
40
+
41
+ run Flipper::Api.app { |builder|
42
+ builder.use Flipper::Middleware::Memoizer, preload: true
43
+ }
@@ -3,9 +3,7 @@ require 'flipper'
3
3
 
4
4
  # sets up default adapter so Flipper works like Flipper::DSL
5
5
  Flipper.configure do |config|
6
- config.default do
7
- Flipper.new Flipper::Adapters::Memory.new
8
- end
6
+ config.adapter { Flipper::Adapters::Memory.new }
9
7
  end
10
8
 
11
9
  puts Flipper.enabled?(:search) # => false
@@ -0,0 +1,37 @@
1
+ # Quick example of how to keep track of when a feature was last checked.
2
+ require 'bundler/setup'
3
+ require 'securerandom'
4
+ require 'active_support/notifications'
5
+ require 'flipper'
6
+
7
+ class FlipperSubscriber
8
+ def self.stats
9
+ @stats ||= {}
10
+ end
11
+
12
+ def call(name, start, finish, id, payload)
13
+ if payload[:operation] == :enabled?
14
+ feature_name = payload.fetch(:feature_name)
15
+ self.class.stats[feature_name] = Time.now
16
+ end
17
+ end
18
+
19
+ ActiveSupport::Notifications.subscribe(/feature_operation.flipper/, new)
20
+ end
21
+
22
+ Flipper.configure do |config|
23
+ config.default {
24
+ Flipper.new(config.adapter, instrumenter: ActiveSupport::Notifications)
25
+ }
26
+ end
27
+
28
+ Flipper.enabled?(:search)
29
+ Flipper.enabled?(:booyeah)
30
+ Flipper.enabled?(:hooray)
31
+ sleep 1
32
+ Flipper.enabled?(:booyeah)
33
+ Flipper.enabled?(:hooray)
34
+ sleep 1
35
+ Flipper.enabled?(:hooray)
36
+
37
+ pp FlipperSubscriber.stats
@@ -4,12 +4,9 @@ require 'flipper/adapters/operation_logger'
4
4
  require 'flipper/instrumentation/log_subscriber'
5
5
 
6
6
  Flipper.configure do |config|
7
- config.default do
7
+ config.adapter do
8
8
  # pick an adapter, this uses memory, any will do
9
- adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
10
-
11
- # pass adapter to handy DSL instance
12
- Flipper.new(adapter)
9
+ Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
13
10
  end
14
11
  end
15
12
 
@@ -216,3 +216,7 @@ module Flipper
216
216
  end
217
217
  end
218
218
  end
219
+
220
+ Flipper.configure do |config|
221
+ config.adapter { Flipper::Adapters::PStore.new }
222
+ end
@@ -37,7 +37,8 @@ module Flipper
37
37
  # Sync all the gate values.
38
38
  remote_get_all.each do |feature_key, remote_gates_hash|
39
39
  feature = Feature.new(feature_key, @local)
40
- local_gates_hash = local_get_all[feature_key] || @local.default_config
40
+ # Check if feature_key is in hash before accessing to prevent unintended hash modification
41
+ local_gates_hash = local_get_all.key?(feature_key) ? local_get_all[feature_key] : @local.default_config
41
42
  local_gate_values = GateValues.new(local_gates_hash)
42
43
  remote_gate_values = GateValues.new(remote_gates_hash)
43
44
  FeatureSynchronizer.new(feature, local_gate_values, remote_gate_values).call
@@ -1,7 +1,33 @@
1
1
  module Flipper
2
2
  class Configuration
3
- def initialize
4
- @default = -> { Flipper.new(Flipper::Adapters::Memory.new) }
3
+ def initialize(options = {})
4
+ @default = -> { Flipper.new(adapter) }
5
+ @adapter = -> { Flipper::Adapters::Memory.new }
6
+ end
7
+
8
+ # The default adapter to use.
9
+ #
10
+ # Pass a block to assign the adapter, and invoke without a block to
11
+ # return the configured adapter instance.
12
+ #
13
+ # Flipper.configure do |config|
14
+ # config.adapter # => instance of default Memory adapter
15
+ #
16
+ # # Configure it to use the ActiveRecord adapter
17
+ # config.adapter do
18
+ # require "flipper/adapters/active_record"
19
+ # Flipper::Adapters::ActiveRecord.new
20
+ # end
21
+ #
22
+ # config.adapter # => instance of ActiveRecord adapter
23
+ # end
24
+ #
25
+ def adapter(&block)
26
+ if block_given?
27
+ @adapter = block
28
+ else
29
+ @adapter.call
30
+ end
5
31
  end
6
32
 
7
33
  # Controls the default instance for flipper. When used with a block it
@@ -13,7 +39,7 @@ module Flipper
13
39
  #
14
40
  # # sets the default block to generate a new instance using ActiveRecord adapter
15
41
  # configuration.default do
16
- # require "flipper-active_record"
42
+ # require "flipper/adapters/active_record"
17
43
  # Flipper.new(Flipper::Adapters::ActiveRecord.new)
18
44
  # end
19
45
  #
@@ -8,15 +8,14 @@ module Flipper
8
8
  #
9
9
  # app - The app this middleware is included in.
10
10
  # opts - The Hash of options.
11
- # :preload_all - Boolean of whether or not to preload all features.
12
- # :preload - Array of Symbol feature names to preload.
11
+ # :preload - Boolean to preload all features or Array of Symbol feature names to preload.
13
12
  #
14
13
  # Examples
15
14
  #
16
15
  # use Flipper::Middleware::Memoizer
17
16
  #
18
17
  # # using with preload_all features
19
- # use Flipper::Middleware::Memoizer, preload_all: true
18
+ # use Flipper::Middleware::Memoizer, preload: true
20
19
  #
21
20
  # # using with preload specific features
22
21
  # use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
@@ -26,6 +25,11 @@ module Flipper
26
25
  raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
27
26
  end
28
27
 
28
+ if opts[:preload_all]
29
+ warn "Flipper::Middleware::Memoizer: `preload_all` is deprecated, use `preload: true`"
30
+ opts[:preload] = true
31
+ end
32
+
29
33
  @app = app
30
34
  @opts = opts
31
35
  @env_key = opts.fetch(:env_key, 'flipper')
@@ -34,39 +38,50 @@ module Flipper
34
38
  def call(env)
35
39
  request = Rack::Request.new(env)
36
40
 
37
- if skip_memoize?(request)
38
- @app.call(env)
39
- else
41
+ if memoize?(request)
40
42
  memoized_call(env)
43
+ else
44
+ @app.call(env)
41
45
  end
42
46
  end
43
47
 
44
48
  private
45
49
 
46
- def skip_memoize?(request)
47
- @opts[:unless] && @opts[:unless].call(request)
50
+ def memoize?(request)
51
+ if @opts[:if]
52
+ @opts[:if].call(request)
53
+ elsif @opts[:unless]
54
+ !@opts[:unless].call(request)
55
+ else
56
+ true
57
+ end
48
58
  end
49
59
 
50
60
  def memoized_call(env)
51
61
  reset_on_body_close = false
52
62
  flipper = env.fetch(@env_key) { Flipper }
53
- original = flipper.memoizing?
54
- flipper.memoize = true
55
63
 
56
- flipper.preload_all if @opts[:preload_all]
64
+ # Already memoizing. This instance does not need to do anything.
65
+ if flipper.memoizing?
66
+ warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/jnunemaker/flipper/pull/523"
67
+ return @app.call(env)
68
+ end
69
+
70
+ flipper.memoize = true
57
71
 
58
- if (preload = @opts[:preload])
59
- flipper.preload(preload)
72
+ case @opts[:preload]
73
+ when true then flipper.preload_all
74
+ when Array then flipper.preload(@opts[:preload])
60
75
  end
61
76
 
62
77
  response = @app.call(env)
63
78
  response[2] = Rack::BodyProxy.new(response[2]) do
64
- flipper.memoize = original
79
+ flipper.memoize = false
65
80
  end
66
81
  reset_on_body_close = true
67
82
  response
68
83
  ensure
69
- flipper.memoize = original if flipper && !reset_on_body_close
84
+ flipper.memoize = false if flipper && !reset_on_body_close
70
85
  end
71
86
  end
72
87
  end
@@ -0,0 +1,46 @@
1
+ module Flipper
2
+ class Railtie < Rails::Railtie
3
+ config.before_configuration do
4
+ config.flipper = ActiveSupport::OrderedOptions.new.update(
5
+ env_key: "flipper",
6
+ memoize: true,
7
+ preload: true,
8
+ instrumenter: ActiveSupport::Notifications,
9
+ log: true
10
+ )
11
+ end
12
+
13
+ initializer "flipper.default", before: :load_config_initializers do |app|
14
+ Flipper.configure do |config|
15
+ config.default do
16
+ Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
17
+ end
18
+ end
19
+ end
20
+
21
+ initializer "flipper.memoizer", after: :load_config_initializers do |app|
22
+ config = app.config.flipper
23
+
24
+ if config.memoize
25
+ app.middleware.use Flipper::Middleware::Memoizer, {
26
+ env_key: config.env_key,
27
+ preload: config.preload,
28
+ if: config.memoize.respond_to?(:call) ? config.memoize : nil
29
+ }
30
+ end
31
+ end
32
+
33
+ initializer "flipper.log", after: :load_config_initializers do |app|
34
+ config = app.config.flipper
35
+ if config.log && config.instrumenter == ActiveSupport::Notifications
36
+ require "flipper/instrumentation/log_subscriber"
37
+ end
38
+ end
39
+
40
+ initializer "flipper.identifier" do
41
+ ActiveSupport.on_load(:active_record) do
42
+ ActiveRecord::Base.include Flipper::Identifier
43
+ end
44
+ end
45
+ end
46
+ end