flipper 0.20.4 → 0.21.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3728bb1232be334741c8b8540533f6ce19c2cb40875272227f767c7355e0c9ee
4
- data.tar.gz: fb7d7d027142157814d11df17b457bb939a850a52d9d9b3f2c42e38697ce55cf
3
+ metadata.gz: 04567e721e7189ed3a5a28c91833caa438e89c8dc412708c1d5487ebcf43e09e
4
+ data.tar.gz: c31948ae275a36a986352ae8d53e858d63e67590a1cae3831013530446008608
5
5
  SHA512:
6
- metadata.gz: f3ee425699a25053e3b27bc8f7e3c33dc14d7a31c9d1906b3ac026ca2bf7131b7d2c1192744cc296578fd519eefa15556266ba21e8642301f4d2a394ad6b8f5a
7
- data.tar.gz: '06382d14225fe2bae3f67a890b82c0742247ca4404ef521ce3c5faeb9d8085004ac92811554ce742b9a99a12a9ab62036000bcb9787b9daea842d4f0cf5b3651'
6
+ metadata.gz: 30e3b27356e2a24948cb4136c59be5a7a0d0b21631102999affa01c3e969dc037e6ff524df3da4ffc0c39ecd8ffbbb1798bda4593fca21f33aeebefb0236caff
7
+ data.tar.gz: 7cb046ca418ff31219b9d786cd22c0f81de6c37c9efc38a718e9ddcf2c97027de0d1a351ddeeae1a24adf1db081dcb364e0751f4d2881ef126e2a98ff0212b63
@@ -1,5 +1,8 @@
1
1
  name: CI
2
- on: [push, pull_request]
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ pull_request:
3
6
  jobs:
4
7
  build:
5
8
  runs-on: ubuntu-latest
@@ -19,6 +22,7 @@ jobs:
19
22
  RAILS_VERSION: 6.0.0
20
23
  SQLITE3_VERSION: 1.4.1
21
24
  REDIS_URL: redis://localhost:6379/0
25
+ CI: true
22
26
  steps:
23
27
  - name: Setup memcached
24
28
  uses: KeisukeYamashita/memcached-actions@v1
@@ -47,3 +51,7 @@ jobs:
47
51
  run: bundle install --jobs 4 --retry 3
48
52
  - name: Run Rake
49
53
  run: bundle exec rake
54
+ - name: Run Examples
55
+ env:
56
+ FLIPPER_CLOUD_TOKEN: ${{ secrets.FLIPPER_CLOUD_TOKEN }}
57
+ run: script/examples
data/Changelog.md CHANGED
@@ -1,4 +1,14 @@
1
- ## unreleased
1
+ ## 0.21.0
2
+
3
+ ### Additions/Changes
4
+
5
+ * Default to using memory adapter (https://github.com/jnunemaker/flipper/pull/501)
6
+ * Adapters now configured on require when possible (https://github.com/jnunemaker/flipper/pull/502)
7
+ * Added cloud recommendation to flipper-ui. Can be disabled with `Flipper::UI.configure { |config| config.cloud_recommendation = false }`. Just want to raise awareness that more is available if people want it (https://github.com/jnunemaker/flipper/pull/504)
8
+ * Added default `flipper_id` implementation via `Flipper::Identifier` and automatically included it in ActiveRecord and Sequel models (https://github.com/jnunemaker/flipper/pull/505)
9
+ * Deprecate superflous sync_method setting (https://github.com/jnunemaker/flipper/pull/511)
10
+
11
+ ## 0.20.4
2
12
 
3
13
  ### Additions/Changes
4
14
 
data/Gemfile CHANGED
@@ -18,6 +18,7 @@ gem 'minitest', '~> 5.8'
18
18
  gem 'minitest-documentation'
19
19
  gem 'webmock', '~> 3.0'
20
20
  gem 'climate_control'
21
+ gem 'redis-namespace'
21
22
 
22
23
  group(:guard) do
23
24
  gem 'guard', '~> 2.15'
data/README.md CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  # Flipper
4
4
 
5
- Beautiful, performant feature flags for Ruby.
5
+ > Beautiful, performant feature flags for Ruby.
6
+
7
+ Flipper gives you control over who has access to features in your app.
8
+
9
+ * Enable or disable features for everyone, specific actors, groups of actors, a percentage of actors, or a percentage of time.
10
+ * Configure your feature flags from the console or a web UI.
11
+ * Regardless of what data store you are using, Flipper can performantly store your feature flags.
12
+ * Use [Flipper Cloud](#flipper-cloud) to cascade features from multiple environments, share settings with your team, control permissions, keep an audit history, and rollback.
6
13
 
7
14
  Control your software — don't let it control you.
8
15
 
@@ -12,6 +19,10 @@ Add this line to your application's Gemfile:
12
19
 
13
20
  gem 'flipper'
14
21
 
22
+ You'll also want to pick a storage [adapter](#adapters), for example:
23
+
24
+ gem 'flipper-active_record'`
25
+
15
26
  And then execute:
16
27
 
17
28
  $ bundle
@@ -22,9 +33,62 @@ Or install it yourself with:
22
33
 
23
34
  ## Getting Started
24
35
 
36
+ Use `Flipper#enabled?` in your app to check if a feature is enabled.
37
+
38
+ ```ruby
39
+ # check if search is enabled
40
+ if Flipper.enabled? :search, current_user
41
+ puts 'Search away!'
42
+ else
43
+ puts 'No search for you!'
44
+ end
45
+ ```
46
+
47
+ All features are disabled by default, so you'll need to explicitly enable them.
48
+
49
+ #### Enable a feature for everyone
50
+
51
+ ```ruby
52
+ Flipper.enable :search
53
+ ```
54
+
55
+ #### Enable a feature for a specific actor
56
+
57
+ ```ruby
58
+ Flipper.enable_actor :search, current_user
59
+ ```
60
+
61
+ #### Enable a feature for a group of actors
62
+
63
+ First tell Flipper about your groups:
64
+
65
+ ```ruby
66
+ # config/initializers/flipper.rb
67
+ Flipper.register(:admin) do |actor|
68
+ actor.respond_to?(:admin?) && actor.admin?
69
+ end
70
+ ```
71
+
72
+ Then enable the feature for that group:
73
+
74
+ ```ruby
75
+ Flipper.enable_group :search, :admin
76
+ ```
77
+
78
+ #### Enable a feature for a percentage of actors
79
+
80
+ ```ruby
81
+ Flipper.enable_percentage_of_actors :search, 2
82
+ ```
83
+
84
+
85
+ Read more about enabling and disabling features with [Gates](docs/Gates.md). Check out the [examples directory](examples/) for more, and take a peek at the [DSL](lib/flipper/dsl.rb) and [Feature](lib/flipper/feature.rb) classes for code/docs.
86
+
87
+ ## Adapters
88
+
25
89
  Flipper is built on adapters for maximum flexibility. Regardless of what data store you are using, Flipper can performantly store data in it.
26
90
 
27
- To get started, pick one of our [supported adapters](docs/Adapters.md#officially-supported) and follow the instructions:
91
+ Pick one of our [supported adapters](docs/Adapters.md#officially-supported) and follow the installation instructions:
28
92
 
29
93
  * [Active Record](docs/active_record/README.md)
30
94
  * [Sequel](docs/sequel/README.md)
@@ -35,7 +99,7 @@ To get started, pick one of our [supported adapters](docs/Adapters.md#officially
35
99
 
36
100
  Or [roll your own](docs/Adapters.md#roll-your-own). We even provide automatic (rspec and minitest) tests for you, so you know you've built your custom adapter correctly.
37
101
 
38
- Once you've selected an adapter and followed the installation instructions, you should be good to go.
102
+ Read more about [Adapters](docs/Adapters.md).
39
103
 
40
104
  ## Flipper UI
41
105
 
@@ -63,50 +127,12 @@ Or, (even better than OSS + UI) use [Flipper Cloud](https://www.flippercloud.io)
63
127
 
64
128
  Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
65
129
 
66
- ## Examples
67
-
68
- Want to get a quick feel for what it looks like to work with Flipper? Check out the following example or the [examples directory](examples/). You might also want to peek at the [DSL](lib/flipper/dsl.rb) and [Feature](lib/flipper/feature.rb) classes for code/docs.
69
-
70
- ```ruby
71
- require 'flipper'
72
-
73
- Flipper.configure do |config|
74
- config.default do
75
- # pick an adapter, this uses memory, any will do, see docs above
76
- adapter = Flipper::Adapters::Memory.new
77
-
78
- # pass adapter to handy DSL instance
79
- Flipper.new(adapter)
80
- end
81
- end
82
-
83
- # check if search is enabled
84
- if Flipper.enabled?(:search)
85
- puts 'Search away!'
86
- else
87
- puts 'No search for you!'
88
- end
89
-
90
- puts 'Enabling Search...'
91
- Flipper.enable(:search)
92
-
93
- # check if search is enabled
94
- if Flipper.enabled?(:search)
95
- puts 'Search away!'
96
- else
97
- puts 'No search for you!'
98
- end
99
- ```
100
-
101
- ## Docs
130
+ ## Advanced
102
131
 
103
132
  A few miscellaneous docs with more info for the hungry.
104
133
 
105
- * [Gates](docs/Gates.md) - Boolean, Groups, Actors, % of Actors, and % of Time
106
- * [Adapters](docs/Adapters.md) - Mongo, Redis, Cassandra, Active Record...
107
134
  * [Instrumentation](docs/Instrumentation.md) - ActiveSupport::Notifications and Statsd
108
135
  * [Optimization](docs/Optimization.md) - Memoization middleware and Cache adapters
109
- * [Web Interface](docs/ui/README.md) - Point and click...
110
136
  * [API](docs/api/README.md) - HTTP API interface
111
137
  * [Caveats](docs/Caveats.md) - Flipper beware! (see what I did there)
112
138
  * [Docker-Compose](docs/DockerCompose.md) - Using docker-compose in contributing
data/docs/Adapters.md CHANGED
@@ -14,6 +14,7 @@ I plan on supporting the adapters in the flipper repo. Other adapters are welcom
14
14
  * [PStore adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/pstore.rb) – great for when a local file is enough
15
15
  * [read-only adapter](https://github.com/jnunemaker/flipper/blob/master/docs/read-only)
16
16
  * [Redis adapter](https://github.com/jnunemaker/flipper/blob/master/docs/redis)
17
+ * [Rollout adapter](rollout/README.md)
17
18
  * [Sequel adapter](https://github.com/jnunemaker/flipper/blob/master/docs/sequel)
18
19
 
19
20
  ## Community Supported
data/docs/Gates.md CHANGED
@@ -7,76 +7,82 @@ Out of the box several types of enabling are supported. They are checked in this
7
7
  All on or all off. Think top level things like `:stats`, `:search`, `:logging`, etc. Also, an easy way to release a new feature as once a feature is boolean enabled it is on for every situation.
8
8
 
9
9
  ```ruby
10
- flipper = Flipper.new(adapter)
11
- flipper[:stats].enable # turn on
12
- flipper[:stats].disable # turn off
13
- flipper[:stats].enabled? # check
10
+ Flipper.enable :stats # turn on
11
+ Flipper.disable :stats # turn off
12
+ Flipper.enabled? :stats # check
14
13
  ```
15
14
 
16
15
  ## 2. Individual Actor
17
16
 
18
- Turn feature on for individual thing. Think enable feature for someone to test or for a buddy. The only requirement for an individual actor is that it must respond to `flipper_id`.
17
+ Turn feature on for individual thing. Think enable feature for someone to test or for a buddy.
19
18
 
20
19
  ```ruby
21
- flipper = Flipper.new(adapter)
20
+ Flipper.enable_actor :stats, user
21
+ Flipper.enabled? :stats, user # true
22
22
 
23
- flipper[:stats].enable user
24
- flipper[:stats].enabled? user # true
25
-
26
- flipper[:stats].disable user
27
- flipper[:stats].enabled? user # false
23
+ Flipper.disable_actor :stats, user
24
+ Flipper.enabled? :stats, user # false
28
25
 
29
26
  # you can enable anything, does not need to be user or person
30
- flipper[:search].enable group
31
- flipper[:search].enabled? group
32
-
33
- # you can also use shortcut methods
34
- flipper.enable_actor :search, user
35
- flipper.disable_actor :search, user
36
- flipper[:search].enable_actor user
37
- flipper[:search].disable_actor user
38
- ```
27
+ Flipper.enable_actor :search, organization
28
+ Flipper.enabled? :search, organization
39
29
 
40
- The key is to make sure you do not enable two different types of objects for the same feature. Imagine that user has a `flipper_id` of 6 and group has a `flipper_id` of 6. Enabling search for user would automatically enable it for group, as they both have a `flipper_id` of 6.
30
+ # you can also save a reference to a specific feature
31
+ feature = Flipper[:search]
32
+
33
+ feature.enable_actor user
34
+ feature.enabled? user # true
35
+ feature.disable_actor user
36
+ ```
41
37
 
42
- The one exception to this rule is if you have globally unique `flipper_ids`, such as UUIDs. If your `flipper_ids` are unique globally in your entire system, enabling two different types should be safe. Another way around this is to prefix the `flipper_id` with the class name like this:
38
+ The only requirement for an individual actor is that it must have a unique `flipper_id`. Include the `Flipper::Identifier` module for a default implementation which combines the class name and `id` (e.g. `User;6`).
43
39
 
44
40
  ```ruby
45
- class User
46
- def flipper_id
47
- "User;#{id}"
48
- end
41
+ class User < Struct.new(:id)
42
+ include Flipper::Identifier
49
43
  end
50
44
 
51
- class Group
45
+ User.new(5).flipper_id # => "User;5"
46
+ ```
47
+
48
+ You can also define your own implementation:
49
+
50
+ ```
51
+ class Organization < Struct.new(:uuid)
52
52
  def flipper_id
53
- "Group;#{id}"
53
+ uuid
54
54
  end
55
55
  end
56
+
57
+ Organization.new("DEB3D850-39FB-444B-A1E9-404A990FDBE0").flipper_id
58
+ # => "DEB3D850-39FB-444B-A1E9-404A990FDBE0"
56
59
  ```
57
60
 
61
+ Just make sure each type of object has a unique `flipper_id`.
62
+
58
63
  ## 3. Percentage of Actors
59
64
 
60
65
  Turn this on for a percentage of actors (think user, member, account, group, whatever). Consistently on or off for this user as long as percentage increases. Think slow rollout of a new feature to a percentage of things.
61
66
 
62
67
  ```ruby
63
- flipper = Flipper.new(adapter)
64
-
65
- # returns a percentage of actors instance set to 10
66
- percentage = flipper.actors(10)
67
-
68
68
  # turn stats on for 10 percent of users in the system
69
- flipper[:stats].enable percentage
69
+ Flipper.enable :stats, Flipper.actors(10)
70
+ # or
71
+ Flipper.enable_percentage_of_actors :stats, 10
70
72
 
71
73
  # checks if actor's flipper_id is in the enabled percentage by hashing
72
74
  # user.flipper_id.to_s to ensure enabled distribution is smooth
73
- flipper[:stats].enabled? user
75
+ Flipper.enabled? :stats, user
74
76
 
75
- # you can also use shortcut methods
76
- flipper.enable_percentage_of_actors :search, 10
77
- flipper.disable_percentage_of_actors :search # sets to 0
78
- flipper[:search].enable_percentage_of_actors 10
79
- flipper[:search].disable_percentage_of_actors # sets to 0
77
+ Flipper.disable_percentage_of_actors :search # sets to 0
78
+ # or
79
+ Flipper.disable :stats, Flipper.actors(0)
80
+
81
+ # you can also save a reference to a specific feature
82
+ feature = Flipper[:search]
83
+ feature.enable_percentage_of_actors 10
84
+ feature.enabled? user
85
+ feature.disable_percentage_of_actors # sets to 0
80
86
  ```
81
87
 
82
88
  ## 4. Percentage of Time
@@ -84,22 +90,20 @@ flipper[:search].disable_percentage_of_actors # sets to 0
84
90
  Turn this on for a percentage of time. Think load testing new features behind the scenes and such.
85
91
 
86
92
  ```ruby
87
- flipper = Flipper.new(adapter)
88
-
89
- # get percentage of time instance set to 5
90
- percentage = flipper.time(5)
91
-
92
93
  # Register a feature called logging and turn it on for 5 percent of the time.
93
94
  # This could be on during one request and off the next
94
95
  # could even be on first time in request and off second time
95
- flipper[:logging].enable percentage
96
- flipper[:logging].enabled? # this will return true 5% of the time.
97
-
98
- # you can also use shortcut methods
99
- flipper.enable_percentage_of_time :search, 5 # registers a feature called "search" and enables it 5% of the time
100
- flipper.disable_percentage_of_time :search # sets to 0
101
- flipper[:search].enable_percentage_of_time 5
102
- flipper[:search].disable_percentage_of_time # sets to 0
96
+ Flipper.enable_percentage_of_time :logging, 5
97
+
98
+ Flipper.enabled? :logging # this will return true 5% of the time.
99
+
100
+ Flipper.disable_percentage_of_time :logging # sets to 0
101
+
102
+ # you can also save a reference to a specific feature
103
+ feature = Flipper[:search]
104
+ feature.enable_percentage_of_time, 5
105
+ feature.enabled?
106
+ feature.disable_percentage_of_time
103
107
  ```
104
108
 
105
109
  Timeness is not a good idea for enabling new features in the UI. Most of the time you want a feature on or off for a user, but there are definitely times when I have found percentage of time to be very useful.
@@ -114,52 +118,48 @@ Flipper.register(:admins) do |actor|
114
118
  actor.respond_to?(:admin?) && actor.admin?
115
119
  end
116
120
 
117
- flipper = Flipper.new(adapter)
118
-
119
- flipper[:stats].enable flipper.group(:admins) # This registers a stats feature and turns it on for admins (which is anything that returns true from the registered block).
120
- flipper[:stats].disable flipper.group(:admins) # turn off the stats feature for admins
121
+ Flipper.enable_group :stats, :admins # This registers a stats feature and turns it on for admins (which is anything that returns true from the registered block).
122
+ Flipper.disable_group :stats, :admins # turn off the stats feature for admins
121
123
 
122
124
  person = Person.find(params[:id])
123
- flipper[:stats].enabled? person # check if enabled, returns true if person.admin? is true
125
+ Flipper.enabled? :stats, person # check if enabled, returns true if person.admin? is true
124
126
 
125
- # you can also use shortcut methods. This also registers a stats feature and turns it on for admins.
126
- flipper.enable_group :stats, :admins
127
- person = Person.find(params[:id])
128
- flipper[:stats].enabled? person # same as above. check if enabled, returns true if person.admin? is true
129
127
 
130
- flipper.disable_group :stats, :admins
131
- flipper[:stats].enable_group :admins
132
- flipper[:stats].disable_group :admins
128
+ # you can also use shortcut methods. This also registers a stats feature and turns it on for admins.
129
+ feature = Flipper[:search]
130
+ feature.enable_group :admins
131
+ feature.enabled? person
132
+ feature.disable_group :admins
133
133
  ```
134
134
 
135
135
  Here's a quick explanation of the above code block:
136
136
 
137
- ```
137
+ ```ruby
138
138
  Flipper.register(:admins) do |actor|
139
139
  actor.respond_to?(:admin?) && actor.admin?
140
140
  end
141
141
  ```
142
- - The above first registers a group called `admins` which essentially saves a [Proc](http://www.eriktrautman.com/posts/ruby-explained-blocks-procs-and-lambdas-aka-closures) to be called later. The `actor` is an instance of the `Flipper::Types::Actor` that wraps the thing being checked against and `actor.thing` is the original object being checked.
142
+ - The above first registers a group called `admins` which essentially saves a [Proc](http://www.eriktrautman.com/posts/ruby-explained-blocks-procs-and-lambdas-aka-closures) to be called later. The `actor` is an instance of the `Flipper::Types::Actor` that wraps the thing being checked against and `actor.thing` is the original object being checked.
143
143
 
144
- ```
145
- flipper[:stats].enable flipper.group(:admins)
144
+ ```ruby
145
+ Flipper.enable_group :stats, :admins
146
146
  ```
147
147
 
148
148
  - The above enables the stats feature to any object that returns true from the `:admins` proc.
149
149
 
150
- ```
150
+ ```ruby
151
151
  person = Person.find(params[:id])
152
- flipper[:stats].enabled? person # check if person is enabled, returns true if person.admin? is true
152
+ Flipper.enabled? :stats, person # check if person is enabled, returns true if person.admin? is true
153
153
  ```
154
154
 
155
- When the `person` object is passed to the `enabled?` method, it is then passed into the proc. If the proc returns true, the entire statement returns true and so `flipper[:stats].enabled? person` returns true. Whatever logic follows this conditional check is then executed.
155
+ When the `person` object is passed to the `enabled?` method, it is then passed into the proc. If the proc returns true, the entire statement returns true and so `Flipper[:stats].enabled? person` returns true. Whatever logic follows this conditional check is then executed.
156
156
 
157
157
  There is no requirement that the thing yielded to the block be a user model or whatever. It can be anything you want, therefore it is a good idea to check that the thing passed into the group block actually responds to what you are trying to do in the `register` proc.
158
158
 
159
159
  In your application code, you can do something like this now:
160
160
 
161
- ```
162
- if flipper[:stats].enabled?(some_admin)
161
+ ```ruby
162
+ if Flipper.enabled? :stats, some_admin
163
163
  # do thing...
164
164
  else
165
165
  # do not do thing
data/examples/basic.rb CHANGED
@@ -1,17 +1,6 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- Flipper.configure do |config|
6
- config.default do
7
- # pick an adapter, this uses memory, any will do
8
- adapter = Flipper::Adapters::Memory.new
9
-
10
- # pass adapter to handy DSL instance
11
- Flipper.new(adapter)
12
- end
13
- end
14
-
15
4
  # check if search is enabled
16
5
  if Flipper.enabled?(:search)
17
6
  puts 'Search away!'
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
4
  # sets up default adapter so Flipper works like Flipper::DSL
data/examples/dsl.rb CHANGED
@@ -1,20 +1,9 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
-
8
4
  # create a thing with an identifier
9
- class Person
10
- attr_reader :id
11
-
12
- def initialize(id)
13
- @id = id
14
- end
15
-
16
- # Must respond to flipper_id
17
- alias_method :flipper_id, :id
5
+ class Person < Struct.new(:id)
6
+ include Flipper::Identifier
18
7
  end
19
8
 
20
9
  person = Person.new(1)
@@ -22,14 +11,14 @@ person = Person.new(1)
22
11
  puts "Stats are disabled by default\n\n"
23
12
 
24
13
  # is a feature enabled
25
- puts "flipper.enabled? :stats: #{flipper.enabled? :stats}"
14
+ puts "flipper.enabled? :stats: #{Flipper.enabled? :stats}"
26
15
 
27
16
  # is a feature on or off for a particular person
28
- puts "flipper.enabled? :stats, person: #{flipper.enabled? :stats, person}"
17
+ puts "Flipper.enabled? :stats, person: #{Flipper.enabled? :stats, person}"
29
18
 
30
19
  # get at a feature
31
- puts "\nYou can also get an individual feature like this:\nstats = flipper[:stats]\n\n"
32
- stats = flipper[:stats]
20
+ puts "\nYou can also get an individual feature like this:\nstats = Flipper[:stats]\n\n"
21
+ stats = Flipper[:stats]
33
22
 
34
23
  # is that feature enabled
35
24
  puts "stats.enabled?: #{stats.enabled?}"
@@ -39,7 +28,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
39
28
 
40
29
  # enable a feature by name
41
30
  puts "\nEnabling stats\n\n"
42
- flipper.enable :stats
31
+ Flipper.enable :stats
43
32
 
44
33
  # or, you can use the feature to enable
45
34
  stats.enable
@@ -49,7 +38,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
49
38
 
50
39
  # oh, no, let's turn this baby off
51
40
  puts "\nDisabling stats\n\n"
52
- flipper.disable :stats
41
+ Flipper.disable :stats
53
42
 
54
43
  # or we can disable using feature obviously
55
44
  stats.disable
@@ -59,18 +48,18 @@ puts "stats.enabled? person: #{stats.enabled? person}"
59
48
  puts
60
49
 
61
50
  # get an instance of the percentage of time type set to 5
62
- puts flipper.time(5).inspect
51
+ puts Flipper.time(5).inspect
63
52
 
64
53
  # get an instance of the percentage of actors type set to 15
65
- puts flipper.actors(15).inspect
54
+ puts Flipper.actors(15).inspect
66
55
 
67
56
  # get an instance of an actor using an object that responds to flipper_id
68
57
  responds_to_flipper_id = Struct.new(:flipper_id).new(10)
69
- puts flipper.actor(responds_to_flipper_id).inspect
58
+ puts Flipper.actor(responds_to_flipper_id).inspect
70
59
 
71
60
  # get an instance of an actor using an object
72
61
  thing = Struct.new(:flipper_id).new(22)
73
- puts flipper.actor(thing).inspect
62
+ puts Flipper.actor(thing).inspect
74
63
 
75
64
  # register a top level group
76
65
  admins = Flipper.register(:admins) { |actor|
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
4
  # Some class that represents what will be trying to do something
@@ -22,21 +21,15 @@ end
22
21
  user1 = User.new(1, true)
23
22
  user2 = User.new(2, false)
24
23
 
25
- # pick an adapter
26
- adapter = Flipper::Adapters::Memory.new
27
-
28
- # get a handy dsl instance
29
- flipper = Flipper.new(adapter)
30
-
31
24
  Flipper.register :admins do |actor|
32
25
  actor.admin?
33
26
  end
34
27
 
35
- flipper[:search].enable
36
- flipper[:stats].enable_actor user1
37
- flipper[:pro_stats].enable_percentage_of_actors 50
38
- flipper[:tweets].enable_group :admins
39
- flipper[:posts].enable_actor user2
28
+ Flipper.enable :search
29
+ Flipper.enable_actor :stats, user1
30
+ Flipper.enable_percentage_of_actors :pro_stats, 50
31
+ Flipper.enable_group :tweets, :admins
32
+ Flipper.enable_actor :posts, user2
40
33
 
41
- pp flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
42
- pp flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
34
+ pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
35
+ pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
data/examples/group.rb CHANGED
@@ -1,10 +1,7 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Register group
10
7
  Flipper.register(:admins) do |actor|
@@ -35,7 +32,7 @@ puts "Stats for admin: #{stats.enabled?(admin)}"
35
32
  puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
36
33
 
37
34
  puts "\nEnabling Stats for admins...\n\n"
38
- stats.enable(flipper.group(:admins))
35
+ stats.enable_group :admins
39
36
 
40
37
  puts "Stats for admin: #{stats.enabled?(admin)}"
41
38
  puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
@@ -1,10 +1,7 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Register group
10
7
  Flipper.register(:enabled_team_member) do |actor, context|
@@ -15,19 +12,12 @@ Flipper.register(:enabled_team_member) do |actor, context|
15
12
  end
16
13
 
17
14
  # Some class that represents actor that will be trying to do something
18
- class User
19
- attr_reader :id
20
-
21
- def initialize(id)
22
- @id = id
23
- end
24
-
25
- def flipper_id
26
- "User;#{@id}"
27
- end
15
+ class User < Struct.new(:id)
16
+ include Flipper::Identifier
28
17
  end
29
18
 
30
19
  class Team
20
+ include Flipper::Identifier
31
21
  attr_reader :name
32
22
 
33
23
  def self.all
@@ -51,10 +41,6 @@ class Team
51
41
  def member?(actor)
52
42
  @members.map(&:id).include?(actor.id)
53
43
  end
54
-
55
- def flipper_id
56
- "Team:#{@name}"
57
- end
58
44
  end
59
45
 
60
46
  jnunemaker = User.new("jnunemaker")
@@ -1,10 +1,7 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Register group
10
7
  Flipper.register(:team_actor) do |actor|
@@ -12,15 +9,8 @@ Flipper.register(:team_actor) do |actor|
12
9
  end
13
10
 
14
11
  # Some class that represents actor that will be trying to do something
15
- class User
16
- attr_reader :id
17
-
18
- def initialize(id)
19
- @id = id
20
- end
21
-
22
- # Must respond to flipper_id
23
- alias_method :flipper_id, :id
12
+ class User < Struct.new(:id)
13
+ include Flipper::Identifier
24
14
  end
25
15
 
26
16
  class Team
@@ -1,4 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
1
+ require 'bundler/setup'
2
2
  require_relative 'active_record/ar_setup'
3
3
  require 'flipper'
4
4
  require 'flipper/adapters/redis'
@@ -1,10 +1,7 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Some class that represents what will be trying to do something
10
7
  class User
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'securerandom'
4
3
  require 'active_support/notifications'
5
4
 
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
  require 'flipper/adapters/operation_logger'
5
4
  require 'flipper/instrumentation/log_subscriber'
@@ -1,21 +1,11 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Some class that represents what will be trying to do something
10
- class User
11
- attr_reader :id
12
-
13
- def initialize(id)
14
- @id = id
15
- end
16
-
17
- # Must respond to flipper_id
18
- alias_method :flipper_id, :id
7
+ class User < Struct.new(:id)
8
+ include Flipper::Identifier
19
9
  end
20
10
 
21
11
  total = 100_000
@@ -24,10 +14,10 @@ total = 100_000
24
14
  users = (1..total).map { |n| User.new(n) }
25
15
 
26
16
  perform_test = lambda { |number|
27
- flipper[:stats].enable flipper.actors(number)
17
+ Flipper.enable_percentage_of_actors :stats, number
28
18
 
29
19
  enabled = users.map { |user|
30
- flipper[:stats].enabled?(user) ? true : nil
20
+ Flipper.enabled?(:stats, user) ? true : nil
31
21
  }.compact
32
22
 
33
23
  actual = (enabled.size / total.to_f * 100).round(3)
@@ -1,10 +1,6 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
-
8
4
  # Some class that represents what will be trying to do something
9
5
  class User
10
6
  attr_reader :id
@@ -18,15 +14,16 @@ class User
18
14
  end
19
15
 
20
16
  # checking a bunch
21
- gate = Flipper::Gates::PercentageOfActors.new
22
- feature_name = "data_migration"
23
- percentage_enabled = 10
24
17
  total = 20_000
25
18
  enabled = []
19
+ percentage_enabled = 10
20
+
21
+ feature = Flipper[:data_migration]
22
+ feature.enable_percentage_of_actors 10
26
23
 
27
24
  (1..total).each do |id|
28
25
  user = User.new(id)
29
- if gate.open?(user, percentage_enabled, feature_name: feature_name)
26
+ if feature.enabled? user
30
27
  enabled << user
31
28
  end
32
29
  end
@@ -35,4 +32,4 @@ p actual: enabled.size, expected: total * (percentage_enabled * 0.01)
35
32
 
36
33
  # checking one
37
34
  user = User.new(1)
38
- p user_1_enabled: Flipper::Gates::PercentageOfActors.new.open?(user, percentage_enabled, feature_name: feature_name)
35
+ p user_1_enabled: feature.enabled?(user)
@@ -3,25 +3,12 @@
3
3
  # feature for actors in a particular location or on a particular plan, but only
4
4
  # for a percentage of them. The percentage is a constant, but could easily be
5
5
  # plucked from memcached, redis, mysql or whatever.
6
- require File.expand_path('../example_setup', __FILE__)
6
+ require 'bundler/setup'
7
7
  require 'flipper'
8
8
 
9
- adapter = Flipper::Adapters::Memory.new
10
- flipper = Flipper.new(adapter)
11
- stats = flipper[:stats]
12
-
13
9
  # Some class that represents what will be trying to do something
14
- class User
15
- attr_reader :id
16
-
17
- def initialize(id)
18
- @id = id
19
- end
20
-
21
- # Must respond to flipper_id
22
- def flipper_id
23
- "User;#{@id}"
24
- end
10
+ class User < Struct.new(:id)
11
+ include Flipper::Identifier
25
12
  end
26
13
 
27
14
  PERCENTAGE = 50
@@ -34,13 +21,13 @@ Flipper.register(:experimental) do |actor|
34
21
  end
35
22
 
36
23
  # enable the experimental group
37
- flipper[:stats].enable_group :experimental
24
+ Flipper.enable_group :stats, :experimental
38
25
 
39
26
  # create a bunch of fake users and see how many are enabled
40
27
  total = 10_000
41
28
  users = (1..total).map { |n| User.new(n) }
42
29
  enabled = users.map { |user|
43
- flipper[:stats].enabled?(user) ? true : nil
30
+ Flipper.enabled?(:stats, user) ? true : nil
44
31
  }.compact
45
32
 
46
33
  # show the results
@@ -1,13 +1,10 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- logging = flipper[:logging]
4
+ logging = Flipper[:logging]
8
5
 
9
6
  perform_test = lambda do |number|
10
- logging.enable flipper.time(number)
7
+ logging.enable_percentage_of_time number
11
8
 
12
9
  total = 100_000
13
10
  enabled = []
data/lib/flipper.rb CHANGED
@@ -152,6 +152,7 @@ require 'flipper/feature'
152
152
  require 'flipper/gate'
153
153
  require 'flipper/instrumenters/memory'
154
154
  require 'flipper/instrumenters/noop'
155
+ require 'flipper/identifier'
155
156
  require 'flipper/middleware/memoizer'
156
157
  require 'flipper/middleware/setup_env'
157
158
  require 'flipper/registry'
@@ -1,7 +1,7 @@
1
1
  module Flipper
2
2
  class Configuration
3
3
  def initialize
4
- @default = -> { raise DefaultNotSet }
4
+ @default = -> { Flipper.new(Flipper::Adapters::Memory.new) }
5
5
  end
6
6
 
7
7
  # Controls the default instance for flipper. When used with a block it
@@ -9,15 +9,15 @@ module Flipper
9
9
  # without a block, it performs a block invocation and returns the result.
10
10
  #
11
11
  # configuration = Flipper::Configuration.new
12
- # configuration.default # => raises DefaultNotSet error.
12
+ # configuration.default # => Flipper::DSL instance using Memory adapter
13
13
  #
14
- # # sets the default block to generate a new instance using Memory adapter
14
+ # # sets the default block to generate a new instance using ActiveRecord adapter
15
15
  # configuration.default do
16
- # require "flipper/adapters/memory"
17
- # Flipper.new(Flipper::Adapters::Memory.new)
16
+ # require "flipper-active_record"
17
+ # Flipper.new(Flipper::Adapters::ActiveRecord.new)
18
18
  # end
19
19
  #
20
- # configuration.default # => Flipper::DSL instance using Memory adapter
20
+ # configuration.default # => Flipper::DSL instance using ActiveRecord adapter
21
21
  #
22
22
  # Returns result of default block invocation if called without block. If
23
23
  # called with block, assigns the default block.
@@ -16,9 +16,8 @@ module Flipper
16
16
  # use it.
17
17
  class DefaultNotSet < Flipper::Error
18
18
  def initialize(message = nil)
19
- default = "Default flipper instance not configured. See " \
20
- "Flipper.configure for how to configure the default instance."
21
- super(message || default)
19
+ warn "Flipper::DefaultNotSet is deprecated and will be removed in 1.0"
20
+ super
22
21
  end
23
22
  end
24
23
 
@@ -0,0 +1,17 @@
1
+ module Flipper
2
+ # A default implementation of `#flipper_id` for actors.
3
+ #
4
+ # class User < Struct.new(:id)
5
+ # include Flipper::Identifier
6
+ # end
7
+ #
8
+ # user = User.new(99)
9
+ # Flipper.enable :thing, user
10
+ # Flipper.enabled? :thing, user #=> true
11
+ #
12
+ module Identifier
13
+ def flipper_id
14
+ "#{self.class.name};#{id}"
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.4'.freeze
2
+ VERSION = '0.21.0.rc1'.freeze
3
3
  end
@@ -3,12 +3,15 @@ require 'flipper/configuration'
3
3
 
4
4
  RSpec.describe Flipper::Configuration do
5
5
  describe '#default' do
6
- it 'raises if default not configured' do
7
- expect { subject.default }.to raise_error(Flipper::DefaultNotSet)
6
+ it 'returns instance using Memory adapter' do
7
+ expect(subject.default).to be_a(Flipper::DSL)
8
+ # All adapter are wrapped in Memoizable
9
+ expect(subject.default.adapter.adapter).to be_a(Flipper::Adapters::Memory)
8
10
  end
9
11
 
10
12
  it 'can be set default' do
11
13
  instance = Flipper.new(Flipper::Adapters::Memory.new)
14
+ expect(subject.default).not_to be(instance)
12
15
  subject.default { instance }
13
16
  expect(subject.default).to be(instance)
14
17
  end
@@ -0,0 +1,14 @@
1
+ require 'helper'
2
+ require 'flipper/identifier'
3
+
4
+ RSpec.describe Flipper::Identifier do
5
+ describe '#flipper_id' do
6
+ class User < Struct.new(:id)
7
+ include Flipper::Identifier
8
+ end
9
+
10
+ it 'uses class name and id' do
11
+ expect(User.new(5).flipper_id).to eq('User;5')
12
+ end
13
+ end
14
+ end
@@ -342,7 +342,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
342
342
  it 'eagerly caches known features for duration of request' do
343
343
  memory = Flipper::Adapters::Memory.new
344
344
  logged_memory = Flipper::Adapters::OperationLogger.new(memory)
345
- cache = ActiveSupport::Cache::DalliStore.new(ENV['MEMCACHED_URL'])
345
+ cache = ActiveSupport::Cache::MemoryStore.new
346
346
  cache.clear
347
347
  cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache, expires_in: 10)
348
348
  logged_cached = Flipper::Adapters::OperationLogger.new(cached)
@@ -93,20 +93,4 @@ RSpec.describe Flipper::Middleware::SetupEnv do
93
93
  expect(last_response.body).to eq(Flipper.object_id.to_s)
94
94
  end
95
95
  end
96
-
97
- context 'when flipper instance or block are nil and default Flipper is NOT configured' do
98
- let(:app) do
99
- app = lambda do |env|
100
- [200, { 'Content-Type' => 'text/html' }, [env['flipper'].enabled?(:search)]]
101
- end
102
- builder = Rack::Builder.new
103
- builder.use described_class
104
- builder.run app
105
- builder
106
- end
107
-
108
- it 'can use env flipper' do
109
- expect { get '/' }.to raise_error(Flipper::DefaultNotSet)
110
- end
111
- end
112
96
  end
data/spec/flipper_spec.rb CHANGED
@@ -232,7 +232,6 @@ RSpec.describe Flipper do
232
232
  cloud_configuration = Flipper::Cloud::Configuration.new({
233
233
  token: "asdf",
234
234
  sync_secret: "tasty",
235
- sync_method: :webhook,
236
235
  })
237
236
 
238
237
  described_class.configure do |config|
@@ -64,6 +64,9 @@ module SpecHelpers
64
64
  end
65
65
 
66
66
  RSpec.configure do |config|
67
+ config.order = :random
68
+ Kernel.srand config.seed
69
+
67
70
  config.include Rack::Test::Methods
68
71
  config.include SpecHelpers
69
72
  end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'bundler/setup'
1
2
  require 'flipper'
2
3
  require 'minitest/autorun'
3
4
  require 'minitest/unit'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.4
4
+ version: 0.21.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-06 00:00:00.000000000 Z
11
+ date: 2021-05-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -41,7 +41,6 @@ files:
41
41
  - examples/configuring_default.rb
42
42
  - examples/dsl.rb
43
43
  - examples/enabled_for_actor.rb
44
- - examples/example_setup.rb
45
44
  - examples/group.rb
46
45
  - examples/group_dynamic_lookup.rb
47
46
  - examples/group_with_members.rb
@@ -83,6 +82,7 @@ files:
83
82
  - lib/flipper/gates/group.rb
84
83
  - lib/flipper/gates/percentage_of_actors.rb
85
84
  - lib/flipper/gates/percentage_of_time.rb
85
+ - lib/flipper/identifier.rb
86
86
  - lib/flipper/instrumentation/log_subscriber.rb
87
87
  - lib/flipper/instrumentation/statsd.rb
88
88
  - lib/flipper/instrumentation/statsd_subscriber.rb
@@ -130,6 +130,7 @@ files:
130
130
  - spec/flipper/gates/group_spec.rb
131
131
  - spec/flipper/gates/percentage_of_actors_spec.rb
132
132
  - spec/flipper/gates/percentage_of_time_spec.rb
133
+ - spec/flipper/identifier_spec.rb
133
134
  - spec/flipper/instrumentation/log_subscriber_spec.rb
134
135
  - spec/flipper/instrumentation/statsd_subscriber_spec.rb
135
136
  - spec/flipper/instrumenters/memory_spec.rb
@@ -170,9 +171,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
170
171
  version: '0'
171
172
  required_rubygems_version: !ruby/object:Gem::Requirement
172
173
  requirements:
173
- - - ">="
174
+ - - ">"
174
175
  - !ruby/object:Gem::Version
175
- version: '0'
176
+ version: 1.3.1
176
177
  requirements: []
177
178
  rubygems_version: 3.0.3
178
179
  signing_key:
@@ -205,6 +206,7 @@ test_files:
205
206
  - spec/flipper/gates/group_spec.rb
206
207
  - spec/flipper/gates/percentage_of_actors_spec.rb
207
208
  - spec/flipper/gates/percentage_of_time_spec.rb
209
+ - spec/flipper/identifier_spec.rb
208
210
  - spec/flipper/instrumentation/log_subscriber_spec.rb
209
211
  - spec/flipper/instrumentation/statsd_subscriber_spec.rb
210
212
  - spec/flipper/instrumenters/memory_spec.rb
@@ -1,8 +0,0 @@
1
- # Nothing to see here... move along.
2
- # Sets up load path for examples and requires some stuff
3
- require 'pp'
4
- require 'pathname'
5
-
6
- root_path = Pathname(__FILE__).dirname.join('..').expand_path
7
- lib_path = root_path.join('lib')
8
- $:.unshift(lib_path)