flipper 0.20.0.beta3 → 0.20.4

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: 832cad2e78b6d06a366c79f035f3c9a546c73ac158c88e51f3f10fc24ab2cfed
4
- data.tar.gz: cf002bed695de325a1c90e959371f9ac7a18bca936c512f686d2a9cf3fdc6ab0
3
+ metadata.gz: 3728bb1232be334741c8b8540533f6ce19c2cb40875272227f767c7355e0c9ee
4
+ data.tar.gz: fb7d7d027142157814d11df17b457bb939a850a52d9d9b3f2c42e38697ce55cf
5
5
  SHA512:
6
- metadata.gz: 2eb3b64a40a69437979fd96e6efda94c0a322181c4f2ca554cf0fb0ac65380ad8c0244b392212dc22d5398fab6c25cf713f117f369a68f93caa460c46d998bf9
7
- data.tar.gz: 60a46b36240f1d55b85ada08d68e8b5c82dc0e8bb51ef8f143362a0237b0d2cfd2585d7dabb7fd6e9aad32712be22d9507926e2a14cd4943c663ea6a56b36674
6
+ metadata.gz: f3ee425699a25053e3b27bc8f7e3c33dc14d7a31c9d1906b3ac026ca2bf7131b7d2c1192744cc296578fd519eefa15556266ba21e8642301f4d2a394ad6b8f5a
7
+ data.tar.gz: '06382d14225fe2bae3f67a890b82c0742247ca4404ef521ce3c5faeb9d8085004ac92811554ce742b9a99a12a9ab62036000bcb9787b9daea842d4f0cf5b3651'
@@ -0,0 +1,49 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ build:
5
+ runs-on: ubuntu-latest
6
+ services:
7
+ redis:
8
+ image: redis
9
+ ports: ['6379:6379']
10
+ options: >-
11
+ --health-cmd "redis-cli ping"
12
+ --health-interval 10s
13
+ --health-timeout 5s
14
+ --health-retries 5
15
+ strategy:
16
+ matrix:
17
+ ruby: ['2.5', '2.6', '2.7']
18
+ env:
19
+ RAILS_VERSION: 6.0.0
20
+ SQLITE3_VERSION: 1.4.1
21
+ REDIS_URL: redis://localhost:6379/0
22
+ steps:
23
+ - name: Setup memcached
24
+ uses: KeisukeYamashita/memcached-actions@v1
25
+ - name: Start MongoDB
26
+ uses: supercharge/mongodb-github-action@1.3.0
27
+ with:
28
+ mongodb-version: 4.0
29
+ - name: Check out repository code
30
+ uses: actions/checkout@v2
31
+ - name: Do some action caching
32
+ uses: actions/cache@v1
33
+ with:
34
+ path: vendor/bundle
35
+ key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
36
+ restore-keys: |
37
+ ${{ runner.os }}-gem-
38
+ - name: Set up Ruby
39
+ uses: actions/setup-ruby@v1
40
+ with:
41
+ ruby-version: ${{ matrix.ruby }}
42
+ - name: Install libpq-dev
43
+ run: sudo apt-get -yqq install libpq-dev
44
+ - name: Install bundler
45
+ run: gem install bundler
46
+ - name: Run bundler
47
+ run: bundle install --jobs 4 --retry 3
48
+ - name: Run Rake
49
+ run: bundle exec rake
data/Changelog.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## unreleased
2
+
3
+ ### Additions/Changes
4
+
5
+ * Allow actors and time gates to deal with decimal percentages (https://github.com/jnunemaker/flipper/pull/492)
6
+ * Change Flipper::Cloud::Middleware to receive webhooks at / in addition to /webhooks.
7
+
8
+ ## 0.20.3
9
+
10
+ ### Additions/Changes
11
+
12
+ * Changed the internal structure of how the memory adapter stores things.
13
+
14
+ ## 0.20.2
15
+
16
+ ### Additions/Changes
17
+
18
+ * Http adapter now raises error when enable/disable/add/remove/clear fail.
19
+ * Cloud adapter sends some extra info like hostname, ruby version, etc. for debugging and decision making.
20
+
21
+ ## 0.20.1
22
+
23
+ ### Additions/Changes
24
+
25
+ * Just a minor tweak to cloud webhook middleware to provide more debugging information about why a hook wasn't successful.
26
+
27
+ ## 0.20.0
28
+
29
+ ### Additions/Changes
30
+
31
+ * Add support for webhooks to `Flipper::Cloud` (https://github.com/jnunemaker/flipper/pull/489).
32
+
1
33
  ## 0.19.1
2
34
 
3
35
  ### Additions/Changes
data/README.md CHANGED
@@ -1,23 +1,10 @@
1
- <pre>
2
- __
3
- _.-~ )
4
- _..--~~~~,' ,-/ _
5
- .-'. . . .' ,-',' ,' )
6
- ,'. . . _ ,--~,-'__..-' ,'
7
- ,'. . . (@)' ---~~~~ ,'
8
- /. . . . '~~ ,-'
9
- /. . . . . ,-'
10
- ; . . . . - . ,'
11
- : . . . . _ /
12
- . . . . . `-.:
13
- . . . ./ - . )
14
- . . . | _____..---.._/ _____
15
- ~---~~~~----~~~~ ~~
16
- </pre>
17
-
18
- Feature flipping is the act of enabling or disabling features or parts of your application, ideally without re-deploying or changing anything in your code base.
19
-
20
- The goal of this gem is to make turning features on or off so easy that everyone does it. Whatever your data store, throughput, or experience, feature flipping should be easy and have minimal impact on your application.
1
+ [![Flipper Mark](docs/images/banner.jpg)](https://www.flippercloud.io)
2
+
3
+ # Flipper
4
+
5
+ Beautiful, performant feature flags for Ruby.
6
+
7
+ Control your software &mdash; don't let it control you.
21
8
 
22
9
  ## Installation
23
10
 
@@ -33,16 +20,59 @@ Or install it yourself with:
33
20
 
34
21
  $ gem install flipper
35
22
 
23
+ ## Getting Started
24
+
25
+ Flipper is built on adapters for maximum flexibility. Regardless of what data store you are using, Flipper can performantly store data in it.
26
+
27
+ To get started, pick one of our [supported adapters](docs/Adapters.md#officially-supported) and follow the instructions:
28
+
29
+ * [Active Record](docs/active_record/README.md)
30
+ * [Sequel](docs/sequel/README.md)
31
+ * [Redis](docs/redis/README.md)
32
+ * [Mongo](docs/mongo/README.md)
33
+ * [Moneta](docs/moneta/README.md)
34
+ * [Rollout](docs/rollout/README.md)
35
+
36
+ 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
+
38
+ Once you've selected an adapter and followed the installation instructions, you should be good to go.
39
+
40
+ ## Flipper UI
41
+
42
+ If you prefer a web UI to an IRB console, you can setup the [Flipper UI](docs/ui/README.md).
43
+
44
+ It's simple and pretty.
45
+
46
+ ![Flipper UI Screenshot](docs/ui/images/feature.png)
47
+
48
+
49
+
50
+ ## Flipper Cloud
51
+
52
+ Or, (even better than OSS + UI) use [Flipper Cloud](https://www.flippercloud.io) which comes with:
53
+
54
+ * **everything in one place** &mdash; no need to bounce around from different application UIs or IRB consoles.
55
+ * **permissions** &mdash; grant access to everyone in your organization or lockdown each project to particular people.
56
+ * **multiple environments** &mdash; production, staging, enterprise, by continent, whatever you need.
57
+ * **personal environments** &mdash; no more rake scripts or manual enable/disable to get your laptop to look like production. Every developer gets a personal environment that inherits from production that they can override as they please ([read more](https://www.johnnunemaker.com/flipper-cloud-environments/)).
58
+ * **no maintenance** &mdash; we'll keep the lights on for you. We also have handy webhooks for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
59
+ * **audit history** &mdash; every feature change and who made it.
60
+ * **rollbacks** &mdash; enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
61
+
62
+ [![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io)
63
+
64
+ Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
65
+
36
66
  ## Examples
37
67
 
38
- The goal of the API for flipper was to have everything revolve around features and what ways they can be enabled. Start with top level and dig into a feature, then dig in further and enable that feature for a given type of access, as opposed to thinking about how the feature will be accessed first (ie: `stats.enable` vs `activate_group(:stats, ...)`).
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.
39
69
 
40
70
  ```ruby
41
71
  require 'flipper'
42
72
 
43
73
  Flipper.configure do |config|
44
74
  config.default do
45
- # pick an adapter, this uses memory, any will do
75
+ # pick an adapter, this uses memory, any will do, see docs above
46
76
  adapter = Flipper::Adapters::Memory.new
47
77
 
48
78
  # pass adapter to handy DSL instance
@@ -68,10 +98,10 @@ else
68
98
  end
69
99
  ```
70
100
 
71
- Of course there are more [examples for you to peruse](examples/). You could also check out the [DSL](lib/flipper/dsl.rb) and [Feature](lib/flipper/feature.rb) classes for code/docs.
72
-
73
101
  ## Docs
74
102
 
103
+ A few miscellaneous docs with more info for the hungry.
104
+
75
105
  * [Gates](docs/Gates.md) - Boolean, Groups, Actors, % of Actors, and % of Time
76
106
  * [Adapters](docs/Adapters.md) - Mongo, Redis, Cassandra, Active Record...
77
107
  * [Instrumentation](docs/Instrumentation.md) - ActiveSupport::Notifications and Statsd
data/docs/Optimization.md CHANGED
@@ -7,7 +7,7 @@ One optimization that flipper provides is a memoizing middleware. The memoizing
7
7
  You can use the middleware like so for Rails:
8
8
 
9
9
  ```ruby
10
- # setup default instance (perhaps in config/initializer/flipper.rb)
10
+ # setup default instance (perhaps in config/initializers/flipper.rb)
11
11
  Flipper.configure do |config|
12
12
  config.default do
13
13
  Flipper.new(...)
@@ -15,7 +15,7 @@ Flipper.configure do |config|
15
15
  end
16
16
 
17
17
  # This assumes you setup a default flipper instance using configure.
18
- config.middleware.use Flipper::Middleware::Memoizer
18
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
19
19
  ```
20
20
 
21
21
  **Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
@@ -23,8 +23,8 @@ config.middleware.use Flipper::Middleware::Memoizer
23
23
  **Also Note**: If you haven't setup a default instance, you can pass the instance to `SetupEnv` as `Memoizer` uses whatever is setup in the `env`:
24
24
 
25
25
  ```ruby
26
- config.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
- config.middleware.use Flipper::Middleware::Memoizer
26
+ Rails.configuration.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
28
28
  ```
29
29
 
30
30
  ### Options
@@ -33,18 +33,18 @@ The Memoizer middleware also supports a few options. Use either `preload` or `pr
33
33
 
34
34
  * **`:preload`** - An `Array` of feature names (`Symbol`) to preload for every request. Useful if you have features that are used on every endpoint. `preload` uses `Adapter#get_multi` to attempt to load the features in one network call instead of N+1 network calls.
35
35
  ```ruby
36
- config.middleware.use Flipper::Middleware::Memoizer,
36
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
37
37
  preload: [:stats, :search, :some_feature]
38
38
  ```
39
39
  * **`:preload_all`** - A Boolean value (default: false) of whether or not all features should be preloaded. Using this results in a `preload_all` call with the result of `Adapter#get_all`. Any subsequent feature checks will be memoized and perform no network calls. I wouldn't recommend using this unless you have few features (< 100?) and nearly all of them are used on every request.
40
40
  ```ruby
41
- config.middleware.use Flipper::Middleware::Memoizer,
41
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
42
42
  preload_all: true
43
43
  ```
44
44
  * **`:unless`** - A block that prevents preloading and memoization if it evaluates to true.
45
45
  ```ruby
46
46
  # skip preloading and memoizing if path starts with /assets
47
- config.middleware.use Flipper::Middleware::Memoizer,
47
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
48
48
  unless: ->(request) { request.path.start_with?("/assets") }
49
49
  ```
50
50
 
Binary file
@@ -35,12 +35,6 @@ module Flipper
35
35
  end
36
36
  end
37
37
 
38
- def add(feature)
39
- body = JSON.generate(name: feature.key)
40
- response = @client.post('/features', body)
41
- response.is_a?(Net::HTTPOK)
42
- end
43
-
44
38
  def get_multi(features)
45
39
  csv_keys = features.map(&:key).join(',')
46
40
  response = @client.get("/features?keys=#{csv_keys}")
@@ -87,51 +81,61 @@ module Flipper
87
81
  parsed_response['features'].map { |feature| feature['key'] }.to_set
88
82
  end
89
83
 
84
+ def add(feature)
85
+ body = JSON.generate(name: feature.key)
86
+ response = @client.post('/features', body)
87
+ raise Error, response unless response.is_a?(Net::HTTPOK)
88
+ true
89
+ end
90
+
90
91
  def remove(feature)
91
92
  response = @client.delete("/features/#{feature.key}")
92
- response.is_a?(Net::HTTPNoContent)
93
+ raise Error, response unless response.is_a?(Net::HTTPNoContent)
94
+ true
93
95
  end
94
96
 
95
97
  def enable(feature, gate, thing)
96
98
  body = request_body_for_gate(gate, thing.value.to_s)
97
99
  query_string = gate.key == :groups ? "?allow_unregistered_groups=true" : ""
98
100
  response = @client.post("/features/#{feature.key}/#{gate.key}#{query_string}", body)
99
- response.is_a?(Net::HTTPOK)
101
+ raise Error, response unless response.is_a?(Net::HTTPOK)
102
+ true
100
103
  end
101
104
 
102
105
  def disable(feature, gate, thing)
103
106
  body = request_body_for_gate(gate, thing.value.to_s)
104
107
  query_string = gate.key == :groups ? "?allow_unregistered_groups=true" : ""
105
- response =
106
- case gate.key
107
- when :percentage_of_actors, :percentage_of_time
108
- @client.post("/features/#{feature.key}/#{gate.key}#{query_string}", body)
109
- else
110
- @client.delete("/features/#{feature.key}/#{gate.key}#{query_string}", body)
111
- end
112
- response.is_a?(Net::HTTPOK)
108
+ response = case gate.key
109
+ when :percentage_of_actors, :percentage_of_time
110
+ @client.post("/features/#{feature.key}/#{gate.key}#{query_string}", body)
111
+ else
112
+ @client.delete("/features/#{feature.key}/#{gate.key}#{query_string}", body)
113
+ end
114
+ raise Error, response unless response.is_a?(Net::HTTPOK)
115
+ true
113
116
  end
114
117
 
115
118
  def clear(feature)
116
119
  response = @client.delete("/features/#{feature.key}/clear")
117
- response.is_a?(Net::HTTPNoContent)
120
+ raise Error, response unless response.is_a?(Net::HTTPNoContent)
121
+ true
118
122
  end
119
123
 
120
124
  private
121
125
 
122
126
  def request_body_for_gate(gate, value)
123
127
  data = case gate.key
124
- when :boolean
125
- {}
126
- when :groups
127
- { name: value }
128
- when :actors
129
- { flipper_id: value }
130
- when :percentage_of_actors, :percentage_of_time
131
- { percentage: value }
132
- else
133
- raise "#{gate.key} is not a valid flipper gate key"
134
- end
128
+ when :boolean
129
+ {}
130
+ when :groups
131
+ { name: value }
132
+ when :actors
133
+ { flipper_id: value }
134
+ when :percentage_of_actors, :percentage_of_time
135
+ { percentage: value }
136
+ else
137
+ raise "#{gate.key} is not a valid flipper gate key"
138
+ end
135
139
  JSON.generate(data)
136
140
  end
137
141
 
@@ -20,58 +20,57 @@ module Flipper
20
20
 
21
21
  # Public: The set of known features.
22
22
  def features
23
- read_feature_keys
23
+ @source.keys.to_set
24
24
  end
25
25
 
26
26
  # Public: Adds a feature to the set of known features.
27
27
  def add(feature)
28
- features.add(feature.key)
28
+ @source[feature.key] ||= default_config
29
29
  true
30
30
  end
31
31
 
32
32
  # Public: Removes a feature from the set of known features and clears
33
33
  # all the values for the feature.
34
34
  def remove(feature)
35
- features.delete(feature.name.to_s)
36
- clear(feature)
35
+ @source.delete(feature.key)
37
36
  true
38
37
  end
39
38
 
40
39
  # Public: Clears all the gate values for a feature.
41
40
  def clear(feature)
42
- feature.gates.each do |gate|
43
- delete key(feature, gate)
44
- end
41
+ @source[feature.key] = default_config
45
42
  true
46
43
  end
47
44
 
48
45
  # Public
49
46
  def get(feature)
50
- read_feature(feature)
47
+ @source[feature.key] || default_config
51
48
  end
52
49
 
53
50
  def get_multi(features)
54
- read_many_features(features)
51
+ result = {}
52
+ features.each do |feature|
53
+ result[feature.key] = @source[feature.key] || default_config
54
+ end
55
+ result
55
56
  end
56
57
 
57
58
  def get_all
58
- features = read_feature_keys.map do |key|
59
- Flipper::Feature.new(key, self)
60
- end
61
-
62
- read_many_features(features)
59
+ @source
63
60
  end
64
61
 
65
62
  # Public
66
63
  def enable(feature, gate, thing)
64
+ @source[feature.key] ||= default_config
65
+
67
66
  case gate.data_type
68
67
  when :boolean
69
68
  clear(feature)
70
- write key(feature, gate), thing.value.to_s
69
+ @source[feature.key][gate.key] = thing.value.to_s
71
70
  when :integer
72
- write key(feature, gate), thing.value.to_s
71
+ @source[feature.key][gate.key] = thing.value.to_s
73
72
  when :set
74
- set_add key(feature, gate), thing.value.to_s
73
+ @source[feature.key][gate.key] << thing.value.to_s
75
74
  else
76
75
  raise "#{gate} is not supported by this adapter yet"
77
76
  end
@@ -81,13 +80,15 @@ module Flipper
81
80
 
82
81
  # Public
83
82
  def disable(feature, gate, thing)
83
+ @source[feature.key] ||= default_config
84
+
84
85
  case gate.data_type
85
86
  when :boolean
86
87
  clear(feature)
87
88
  when :integer
88
- write key(feature, gate), thing.value.to_s
89
+ @source[feature.key][gate.key] = thing.value.to_s
89
90
  when :set
90
- set_delete key(feature, gate), thing.value.to_s
91
+ @source[feature.key][gate.key].delete thing.value.to_s
91
92
  else
92
93
  raise "#{gate} is not supported by this adapter yet"
93
94
  end
@@ -103,81 +104,6 @@ module Flipper
103
104
  ]
104
105
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
105
106
  end
106
-
107
- private
108
-
109
- def read_feature_keys
110
- set_members(FeaturesKey)
111
- end
112
-
113
- # Private
114
- def key(feature, gate)
115
- "feature/#{feature.key}/#{gate.key}"
116
- end
117
-
118
- def read_many_features(features)
119
- result = {}
120
- features.each do |feature|
121
- result[feature.key] = read_feature(feature)
122
- end
123
- result
124
- end
125
-
126
- def read_feature(feature)
127
- result = {}
128
-
129
- feature.gates.each do |gate|
130
- result[gate.key] =
131
- case gate.data_type
132
- when :boolean, :integer
133
- read key(feature, gate)
134
- when :set
135
- set_members key(feature, gate)
136
- else
137
- raise "#{gate} is not supported by this adapter yet"
138
- end
139
- end
140
-
141
- result
142
- end
143
-
144
- # Private
145
- def read(key)
146
- @source[key.to_s]
147
- end
148
-
149
- # Private
150
- def write(key, value)
151
- @source[key.to_s] = value.to_s
152
- end
153
-
154
- # Private
155
- def delete(key)
156
- @source.delete(key.to_s)
157
- end
158
-
159
- # Private
160
- def set_add(key, value)
161
- ensure_set_initialized(key)
162
- @source[key.to_s].add(value.to_s)
163
- end
164
-
165
- # Private
166
- def set_delete(key, value)
167
- ensure_set_initialized(key)
168
- @source[key.to_s].delete(value.to_s)
169
- end
170
-
171
- # Private
172
- def set_members(key)
173
- ensure_set_initialized(key)
174
- @source[key.to_s]
175
- end
176
-
177
- # Private
178
- def ensure_set_initialized(key)
179
- @source[key.to_s] ||= Set.new
180
- end
181
107
  end
182
108
  end
183
109
  end
@@ -19,7 +19,7 @@ module Flipper
19
19
  # Public: Initializes a new interval synchronizer.
20
20
  #
21
21
  # synchronizer - The Synchronizer to call when the interval has passed.
22
- # interval - The Integer number of milliseconds between invocations of
22
+ # interval - The Integer number of seconds between invocations of
23
23
  # the wrapped synchronizer.
24
24
  def initialize(synchronizer, interval: nil)
25
25
  @synchronizer = synchronizer
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.0.beta3'.freeze
2
+ VERSION = '0.20.4'.freeze
3
3
  end
@@ -76,9 +76,9 @@ RSpec.describe Flipper::Adapters::Http do
76
76
  .to_return(status: 503, body: "", headers: {})
77
77
 
78
78
  adapter = described_class.new(url: 'http://app.com/flipper')
79
- expect do
79
+ expect {
80
80
  adapter.get(flipper[:feature_panel])
81
- end.to raise_error(Flipper::Adapters::Http::Error)
81
+ }.to raise_error(Flipper::Adapters::Http::Error)
82
82
  end
83
83
  end
84
84
 
@@ -88,9 +88,9 @@ RSpec.describe Flipper::Adapters::Http do
88
88
  .to_return(status: 503, body: "", headers: {})
89
89
 
90
90
  adapter = described_class.new(url: 'http://app.com/flipper')
91
- expect do
91
+ expect {
92
92
  adapter.get_multi([flipper[:feature_panel]])
93
- end.to raise_error(Flipper::Adapters::Http::Error)
93
+ }.to raise_error(Flipper::Adapters::Http::Error)
94
94
  end
95
95
  end
96
96
 
@@ -100,9 +100,9 @@ RSpec.describe Flipper::Adapters::Http do
100
100
  .to_return(status: 503, body: "", headers: {})
101
101
 
102
102
  adapter = described_class.new(url: 'http://app.com/flipper')
103
- expect do
103
+ expect {
104
104
  adapter.get_all
105
- end.to raise_error(Flipper::Adapters::Http::Error)
105
+ }.to raise_error(Flipper::Adapters::Http::Error)
106
106
  end
107
107
  end
108
108
 
@@ -112,9 +112,75 @@ RSpec.describe Flipper::Adapters::Http do
112
112
  .to_return(status: 503, body: "", headers: {})
113
113
 
114
114
  adapter = described_class.new(url: 'http://app.com/flipper')
115
- expect do
115
+ expect {
116
116
  adapter.features
117
- end.to raise_error(Flipper::Adapters::Http::Error)
117
+ }.to raise_error(Flipper::Adapters::Http::Error)
118
+ end
119
+ end
120
+
121
+ describe "#add" do
122
+ it "raises error when not successful" do
123
+ stub_request(:post, /app.com/)
124
+ .to_return(status: 503, body: "{}", headers: {})
125
+
126
+ adapter = described_class.new(url: 'http://app.com/flipper')
127
+ expect {
128
+ adapter.add(Flipper::Feature.new(:search, adapter))
129
+ }.to raise_error(Flipper::Adapters::Http::Error)
130
+ end
131
+ end
132
+
133
+ describe "#remove" do
134
+ it "raises error when not successful" do
135
+ stub_request(:delete, /app.com/)
136
+ .to_return(status: 503, body: "{}", headers: {})
137
+
138
+ adapter = described_class.new(url: 'http://app.com/flipper')
139
+ expect {
140
+ adapter.remove(Flipper::Feature.new(:search, adapter))
141
+ }.to raise_error(Flipper::Adapters::Http::Error)
142
+ end
143
+ end
144
+
145
+ describe "#clear" do
146
+ it "raises error when not successful" do
147
+ stub_request(:delete, /app.com/)
148
+ .to_return(status: 503, body: "{}", headers: {})
149
+
150
+ adapter = described_class.new(url: 'http://app.com/flipper')
151
+ expect {
152
+ adapter.clear(Flipper::Feature.new(:search, adapter))
153
+ }.to raise_error(Flipper::Adapters::Http::Error)
154
+ end
155
+ end
156
+
157
+ describe "#enable" do
158
+ it "raises error when not successful" do
159
+ stub_request(:post, /app.com/)
160
+ .to_return(status: 503, body: "{}", headers: {})
161
+
162
+ adapter = described_class.new(url: 'http://app.com/flipper')
163
+ feature = Flipper::Feature.new(:search, adapter)
164
+ gate = feature.gate(:boolean)
165
+ thing = gate.wrap(true)
166
+ expect {
167
+ adapter.enable(feature, gate, thing)
168
+ }.to raise_error(Flipper::Adapters::Http::Error)
169
+ end
170
+ end
171
+
172
+ describe "#disable" do
173
+ it "raises error when not successful" do
174
+ stub_request(:delete, /app.com/)
175
+ .to_return(status: 503, body: "{}", headers: {})
176
+
177
+ adapter = described_class.new(url: 'http://app.com/flipper')
178
+ feature = Flipper::Feature.new(:search, adapter)
179
+ gate = feature.gate(:boolean)
180
+ thing = gate.wrap(false)
181
+ expect {
182
+ adapter.disable(feature, gate, thing)
183
+ }.to raise_error(Flipper::Adapters::Http::Error)
118
184
  end
119
185
  end
120
186
 
@@ -2,7 +2,27 @@ require 'helper'
2
2
  require 'flipper/spec/shared_adapter_specs'
3
3
 
4
4
  RSpec.describe Flipper::Adapters::Memory do
5
- subject { described_class.new }
5
+ let(:source) { {} }
6
+ subject { described_class.new(source) }
6
7
 
7
8
  it_should_behave_like 'a flipper adapter'
9
+
10
+ it "can initialize from big hash" do
11
+ flipper = Flipper.new(subject)
12
+ flipper.enable :subscriptions
13
+ flipper.disable :search
14
+ flipper.enable_percentage_of_actors :pro_deal, 20
15
+ flipper.enable_percentage_of_time :logging, 30
16
+ flipper.enable_actor :following, Flipper::Actor.new('1')
17
+ flipper.enable_actor :following, Flipper::Actor.new('3')
18
+ flipper.enable_group :following, Flipper::Types::Group.new(:staff)
19
+
20
+ expect(source).to eq({
21
+ "subscriptions" => subject.default_config.merge(boolean: "true"),
22
+ "search" => subject.default_config,
23
+ "logging" => subject.default_config.merge(:percentage_of_time => "30"),
24
+ "pro_deal" => subject.default_config.merge(:percentage_of_actors => "20"),
25
+ "following" => subject.default_config.merge(actors: Set["1", "3"], groups: Set["staff"]),
26
+ })
27
+ end
8
28
  end
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.0.beta3
4
+ version: 0.20.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-17 00:00:00.000000000 Z
11
+ date: 2021-03-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -18,6 +18,7 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - ".codeclimate.yml"
21
+ - ".github/workflows/ci.yml"
21
22
  - CODE_OF_CONDUCT.md
22
23
  - Changelog.md
23
24
  - Dockerfile
@@ -34,6 +35,7 @@ files:
34
35
  - docs/Optimization.md
35
36
  - docs/api/README.md
36
37
  - docs/http/README.md
38
+ - docs/images/banner.jpg
37
39
  - docs/read-only/README.md
38
40
  - examples/basic.rb
39
41
  - examples/configuring_default.rb
@@ -168,9 +170,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
170
  version: '0'
169
171
  required_rubygems_version: !ruby/object:Gem::Requirement
170
172
  requirements:
171
- - - ">"
173
+ - - ">="
172
174
  - !ruby/object:Gem::Version
173
- version: 1.3.1
175
+ version: '0'
174
176
  requirements: []
175
177
  rubygems_version: 3.0.3
176
178
  signing_key: