rollout-redis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/README.md +193 -0
- data/Rakefile +7 -0
- data/lib/rollout/feature.rb +56 -0
- data/lib/rollout/version.rb +5 -0
- data/lib/rollout.rb +170 -0
- data/rollout-redis.gemspec +30 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d7d811416500c11c77ca8c8490d301c0ce266db5dc6e7967e07c25e435be454
|
4
|
+
data.tar.gz: c8e76ea2823845c3c3ef2d1929e4e1e75d0f19688ac99b5e4a6e903dae3a57ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e03a813c99336b16cdee142457f04e56a3cb12b8292f95034a583cd79ae12e97b9586db54cb3070d297bf762259acdb3a9026496124a1a6a40dbcf880fd4433d
|
7
|
+
data.tar.gz: 632ad6cf617ba379149383ad21e1033d5c6c3c12f9f8de58b1c1465d1440e957760f7ac4c15c011886dd9b188c73f2387b16d7cc144f4ece16446183f8dfcc21
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Changelog
|
2
|
+
All changes to `rollout-redis` will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
|
8
|
+
## [0.1.0] - 2023-10-23
|
9
|
+
|
10
|
+
- Initial version
|
data/README.md
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# rollout-redis 🚀
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/rollout-redis.svg)](https://badge.fury.io/rb/rollout-redis)
|
4
|
+
|
5
|
+
Fast and easy feature flags based on Redis.
|
6
|
+
|
7
|
+
Based on the discontinued [rollout](https://github.com/fetlife/rollout) project, removing some capabilities, including some new features and supporting latest Redis versions.
|
8
|
+
|
9
|
+
Topics covered in this README:
|
10
|
+
|
11
|
+
- [Install it](#install-it)
|
12
|
+
- [Quick Start](#quick-start-💨)
|
13
|
+
- [Advanced features](#advanced-features-🦾)
|
14
|
+
- [Gradual activation based on percentages](#gradual-activation-based-on-percentages)
|
15
|
+
- [Caching Feature Flags](#caching-feature-flags)
|
16
|
+
- [Auto-deactivating flags](#auto-deactivating-flags)
|
17
|
+
- [Migrating from rollout gem](#migrating-from-rollout-gem-🚨)
|
18
|
+
- [Changelog](#changelog)
|
19
|
+
- [Contributing](#contributing)
|
20
|
+
|
21
|
+
## Install it
|
22
|
+
|
23
|
+
```bash
|
24
|
+
gem install rollout-redis
|
25
|
+
```
|
26
|
+
|
27
|
+
## Quick Start 💨
|
28
|
+
|
29
|
+
Instantiate the `Rollout` class sending a `Redis` instance as a parameter.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'redis'
|
33
|
+
require 'rollout'
|
34
|
+
|
35
|
+
@redis ||= Redis.new(
|
36
|
+
host: ENV.fetch('REDIS_HOST'),
|
37
|
+
port: ENV.fetch('REDIS_PORT')
|
38
|
+
)
|
39
|
+
@rollout ||= Rollout.new(@redis)
|
40
|
+
```
|
41
|
+
|
42
|
+
Now you can activate Feature Flags:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
@rollout.activate('FEATURE_FLAG_NAME') # => true/false
|
46
|
+
```
|
47
|
+
|
48
|
+
Verify if a feature is currently enabled:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
if @rollout.active?('FEATURE_FLAG_NAME')
|
52
|
+
# your new code here...
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
An alternative to the if check, is to wrap your code under the `with_feature` method. The wrapped code will be performed only if the feature flag is active:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
@rollout.with_feature('FEATURE_FLAG_NAME') do
|
60
|
+
# your new code here...
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
If there is an issue, you have the option to disable a feature:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
@rollout.deactivate('FEATURE_FLAG_NAME')
|
68
|
+
```
|
69
|
+
|
70
|
+
## Advanced features 🦾
|
71
|
+
|
72
|
+
### Gradual activation based on percentages
|
73
|
+
|
74
|
+
When introducing a new feature, it's a recommended practice to gradually enable it for a specific portion of your target audience to evaluate its impact. To achieve this, you can utilize the `activate` method, as shown below:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
@rollout.activate('FEATURE_FLAG_NAME', 20)
|
78
|
+
```
|
79
|
+
|
80
|
+
Now, to know if a feature flags is enabled, you need to provide a determinator (in this example, we're using the user email):
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
if @rollout.active?('FEATURE_FLAG_NAME', user_email)
|
84
|
+
# your new code here...
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
The gradual activation also works wrapping your code within the `with_feature` method, you just need to provde the determinator you want to use.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
@rollout.with_feature('FEATURE_FLAG_NAME', user_email) do
|
92
|
+
# your new code here...
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
It's important to note that if you use the `active?` method without specifying a determinator to determine whether this subset of the audience should see the new feature, it will always return `false` since the activation percentage is less than 100%. See:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
@rollout.activate('FEATURE_FLAG_NAME', 20)
|
100
|
+
@rollout.active?('FEATURE_FLAG_NAME') # => false
|
101
|
+
```
|
102
|
+
|
103
|
+
### Caching Feature Flags
|
104
|
+
|
105
|
+
The Rollout gem is tightly integrated with Redis for feature flag status management. Consequently, occasional connectivity issues between your application and the Redis storage may arise.
|
106
|
+
|
107
|
+
To prevent potential application degradation when the Redis storage is unavailable, you can enable feature flag status caching during the gem's instantiation:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
@rollout ||= Rollout.new(redis).with_cache
|
111
|
+
```
|
112
|
+
|
113
|
+
Additionally, you can specify extra parameters to configure the duration (in seconds) for which the feature flag status is stored in the cache. By default, this duration is set to 300 seconds (5 minutes):
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
@rollout ||= Rollout.new(redis)
|
117
|
+
.with_cache(expires_in: 300)
|
118
|
+
```
|
119
|
+
|
120
|
+
In the case that you need to clear the cache at any point, you can make use of the `clean_cache` method:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
@rollout.clean_cache
|
124
|
+
```
|
125
|
+
|
126
|
+
### Auto-deactivating flags
|
127
|
+
|
128
|
+
If you want to allow the gem to deactivate your feature flag automatically when a threshold of erros is reached, you can enable the degrade feature using the `with_degrade` method.
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
@rollout ||= Rollout.new(redis)
|
132
|
+
.with_cache
|
133
|
+
.with_degrade(sample: 5000, min: 100, threshold: 0.1)
|
134
|
+
```
|
135
|
+
|
136
|
+
So now, instead of using the `active?` method, you need to wrap your new code under the `with_feature` method.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
@rollout.with_feature('FEATURE_FLAG_NAME') do
|
140
|
+
# your new feature code here...
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
When any unexpected error appears during the wrapped code execution, the Rollout gem will take it into account for automatically deactivating the feature flag if the threshold of errors is reached. All the managed or captured errors inside the wrapped code will not be taken into consideration.
|
145
|
+
|
146
|
+
## Migrating from rollout gem 🚨
|
147
|
+
|
148
|
+
If you are currently using the unmaintained [rollout](https://github.com/fetlife/rollout) gem, you should consider checking this [migration guide](https://github.com/jcagarcia/rollout-redis/blob/main/MIGRATING_FROM_ROLLOUT_GEM.md) for start using the new `rollout-redis` gem.
|
149
|
+
|
150
|
+
## Changelog
|
151
|
+
|
152
|
+
If you're interested in seeing the changes and bug fixes between each version of `rollout-redis`, read the [Changelog](https://github.com/jcagarcia/rollout-redis/blob/main/CHANGELOG.md).
|
153
|
+
|
154
|
+
## Contributing
|
155
|
+
|
156
|
+
We welcome and appreciate contributions from the open-source community. Before you get started, please take a moment to review the guidelines below.
|
157
|
+
|
158
|
+
### How to Contribute
|
159
|
+
|
160
|
+
1. Fork the repository.
|
161
|
+
2. Clone the repository to your local machine.
|
162
|
+
3. Create a new branch for your contribution.
|
163
|
+
4. Make your changes and ensure they meet project standards.
|
164
|
+
5. Commit your changes with clear messages.
|
165
|
+
6. Push your branch to your GitHub repository.
|
166
|
+
7. Open a pull request in our repository.
|
167
|
+
8. Participate in code review and address feedback.
|
168
|
+
9. Once approved, your changes will be merged.
|
169
|
+
|
170
|
+
### Development
|
171
|
+
|
172
|
+
This project is dockerized. Once you clone the repository, you can use the `Make` commands to build the project.
|
173
|
+
|
174
|
+
```shell
|
175
|
+
make build
|
176
|
+
```
|
177
|
+
|
178
|
+
You can pass the tests running:
|
179
|
+
|
180
|
+
```shell
|
181
|
+
make test
|
182
|
+
```
|
183
|
+
|
184
|
+
### Issue Tracker
|
185
|
+
|
186
|
+
Open issues on the GitHub issue tracker with clear information.
|
187
|
+
|
188
|
+
### Contributors
|
189
|
+
|
190
|
+
* Juan Carlos GarcÃa - Creator - https://github.com/jcagarcia
|
191
|
+
|
192
|
+
The `rollout-redis` gem is based on the discontinued [rollout](https://github.com/fetlife/rollout) project, created by [James Golick](https://github.com/jamesgolick)
|
193
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
class Rollout
|
6
|
+
class Feature
|
7
|
+
attr_accessor :percentage
|
8
|
+
attr_reader :name, :data
|
9
|
+
|
10
|
+
RAND_BASE = (2**32 - 1) / 100.0
|
11
|
+
|
12
|
+
def initialize(name, data={})
|
13
|
+
@name = name
|
14
|
+
@data = data
|
15
|
+
@percentage = @data[:percentage]
|
16
|
+
end
|
17
|
+
|
18
|
+
def active?(determinator=nil)
|
19
|
+
if determinator
|
20
|
+
determinator_in_percentage?(determinator)
|
21
|
+
else
|
22
|
+
@percentage == 100
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_request
|
27
|
+
if @data[:requests]
|
28
|
+
@data[:requests] = @data[:requests] + 1
|
29
|
+
else
|
30
|
+
@data[:requests] = 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_error
|
35
|
+
if @data[:errors]
|
36
|
+
@data[:errors] = @data[:errors] + 1
|
37
|
+
else
|
38
|
+
@data[:errors] = 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def requests
|
43
|
+
@data[:requests] || 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def errors
|
47
|
+
@data[:errors] || 0
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def determinator_in_percentage?(determinator)
|
53
|
+
Zlib.crc32(determinator) < RAND_BASE * @percentage
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/rollout.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rollout/feature'
|
4
|
+
require 'rollout/version'
|
5
|
+
require 'redis'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
class Rollout
|
9
|
+
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :storage
|
13
|
+
|
14
|
+
def initialize(storage)
|
15
|
+
@storage = storage
|
16
|
+
@cache_enabled = false
|
17
|
+
@degrade_enabled = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_cache(expires_in: 300)
|
21
|
+
@cache_enabled = true
|
22
|
+
@cache_time = expires_in
|
23
|
+
@cache = {}
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_degrade(min: 100, threshold: 0.1)
|
29
|
+
@degrade_enabled = true
|
30
|
+
@degrade_min = min
|
31
|
+
@degrade_threshold = threshold
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def activate(feature_name, percentage=100)
|
37
|
+
data = { percentage: percentage }
|
38
|
+
feature = Feature.new(feature_name, data)
|
39
|
+
@cache[feature_name] = {
|
40
|
+
feature: feature,
|
41
|
+
timestamp: Time.now.to_i
|
42
|
+
} if @cache_enabled
|
43
|
+
save(feature) == "OK"
|
44
|
+
end
|
45
|
+
|
46
|
+
def activate_percentage(feature_name, percentage)
|
47
|
+
activate(feature_name, percentage)
|
48
|
+
end
|
49
|
+
|
50
|
+
def deactivate(feature_name)
|
51
|
+
del(feature_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def active?(feature_name, determinator = nil)
|
55
|
+
feature = get(feature_name, determinator)
|
56
|
+
return false unless feature
|
57
|
+
|
58
|
+
active = feature.active?(determinator)
|
59
|
+
|
60
|
+
if active && @degrade_enabled
|
61
|
+
feature.add_request
|
62
|
+
save(feature)
|
63
|
+
end
|
64
|
+
|
65
|
+
active
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_feature_flag(feature_name, determinator = nil, &block)
|
69
|
+
yield if active?(feature_name, determinator)
|
70
|
+
rescue => e
|
71
|
+
feature = get(feature_name, determinator)
|
72
|
+
if @degrade_enabled && feature
|
73
|
+
feature.add_error
|
74
|
+
save(feature)
|
75
|
+
|
76
|
+
deactivate(feature_name) if degraded?(feature)
|
77
|
+
end
|
78
|
+
raise e
|
79
|
+
end
|
80
|
+
|
81
|
+
def clean_cache
|
82
|
+
return unless @cache_enabled
|
83
|
+
|
84
|
+
@cache = {}
|
85
|
+
end
|
86
|
+
|
87
|
+
def migrate_from_rollout_format
|
88
|
+
keys = @storage.keys('feature:*')
|
89
|
+
|
90
|
+
keys.each do |old_key|
|
91
|
+
new_key = old_key.gsub('feature:', 'feature-rollout-redis:')
|
92
|
+
old_data = @storage.get(old_key)
|
93
|
+
|
94
|
+
if old_data
|
95
|
+
percentage = old_data.split('|')[0].to_i
|
96
|
+
|
97
|
+
new_data = {
|
98
|
+
percentage: percentage,
|
99
|
+
requests: 0,
|
100
|
+
errors: 0
|
101
|
+
}.to_json
|
102
|
+
|
103
|
+
@storage.set(new_key, new_data)
|
104
|
+
|
105
|
+
puts "Migrated key: #{old_key} to #{new_key} with data #{new_data}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def get(feature_name, determinator = nil)
|
113
|
+
feature = from_redis(feature_name)
|
114
|
+
return unless feature
|
115
|
+
|
116
|
+
@cache[feature_name] = {
|
117
|
+
feature: feature,
|
118
|
+
timestamp: Time.now.to_i
|
119
|
+
} if @cache_enabled
|
120
|
+
|
121
|
+
feature
|
122
|
+
rescue ::Redis::BaseError => e
|
123
|
+
cached_feature = from_cache(feature_name)
|
124
|
+
raise Rollout::Error.new(e) unless cached_feature
|
125
|
+
|
126
|
+
cached_feature
|
127
|
+
end
|
128
|
+
|
129
|
+
def save(feature)
|
130
|
+
@storage.set(key(feature.name), feature.data.to_json)
|
131
|
+
end
|
132
|
+
|
133
|
+
def del(feature_name)
|
134
|
+
@storage.del(key(feature_name)) == 1
|
135
|
+
end
|
136
|
+
|
137
|
+
def from_cache(feature_name)
|
138
|
+
return nil unless @cache_enabled
|
139
|
+
|
140
|
+
cached = @cache[feature_name]
|
141
|
+
|
142
|
+
if expired?(cached[:timestamp])
|
143
|
+
@cache.delete(feature_name)
|
144
|
+
return nil
|
145
|
+
end
|
146
|
+
|
147
|
+
cached[:feature]
|
148
|
+
end
|
149
|
+
|
150
|
+
def from_redis(feature_name)
|
151
|
+
data = @storage.get(key(feature_name))
|
152
|
+
return unless data
|
153
|
+
Feature.new(feature_name, JSON.parse(data, symbolize_names: true))
|
154
|
+
end
|
155
|
+
|
156
|
+
def expired?(timestamp)
|
157
|
+
Time.now.to_i - timestamp > @cache_time
|
158
|
+
end
|
159
|
+
|
160
|
+
def degraded?(feature)
|
161
|
+
return false if !@degrade_enabled
|
162
|
+
return false if feature.requests < @degrade_min
|
163
|
+
|
164
|
+
feature.errors > @degrade_threshold * feature.requests
|
165
|
+
end
|
166
|
+
|
167
|
+
def key(name)
|
168
|
+
"feature-rollout-redis:#{name}"
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
require 'rollout/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rollout-redis'
|
8
|
+
spec.version = Rollout::VERSION
|
9
|
+
spec.authors = ['Juan Carlos GarcÃa']
|
10
|
+
spec.email = ['jugade92@gmail.com']
|
11
|
+
spec.description = 'Fast and easy feature flags based on the latest Redis versions.'
|
12
|
+
spec.summary = 'Fast and easy feature flags based on the latest Redis versions.'
|
13
|
+
spec.homepage = 'https://github.com/jcagarcia/rollout-redis'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
files = Dir["lib/**/*.rb", "lib/**/tasks/*.rake"]
|
17
|
+
rootfiles = ["CHANGELOG.md", "rollout-redis.gemspec", "Rakefile", "README.md"]
|
18
|
+
|
19
|
+
spec.files = rootfiles + files
|
20
|
+
spec.executables = []
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.3'
|
24
|
+
|
25
|
+
spec.add_runtime_dependency 'redis', '>= 4.0', '<= 5'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '>= 2.4'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
29
|
+
spec.add_development_dependency 'mock_redis', '~> 0.37'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rollout-redis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Juan Carlos GarcÃa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.4'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.4'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.12'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.12'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: mock_redis
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.37'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.37'
|
75
|
+
description: Fast and easy feature flags based on the latest Redis versions.
|
76
|
+
email:
|
77
|
+
- jugade92@gmail.com
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- CHANGELOG.md
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- lib/rollout.rb
|
86
|
+
- lib/rollout/feature.rb
|
87
|
+
- lib/rollout/version.rb
|
88
|
+
- rollout-redis.gemspec
|
89
|
+
homepage: https://github.com/jcagarcia/rollout-redis
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2.3'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubygems_version: 3.4.10
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Fast and easy feature flags based on the latest Redis versions.
|
112
|
+
test_files: []
|