togls 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/CODE_OF_CONDUCT.md +23 -6
- data/ChangeLog.md +7 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +128 -163
- data/contributing/togls_architecture.monopic +0 -0
- data/lib/tasks/togls.rake +2 -2
- data/lib/togls/errors.rb +3 -0
- data/lib/togls/feature.rb +3 -27
- data/lib/togls/feature_repository.rb +43 -0
- data/lib/togls/feature_repository_drivers/in_memory_driver.rb +17 -0
- data/lib/togls/feature_repository_drivers.rb +4 -0
- data/lib/togls/feature_toggle_registry.rb +46 -0
- data/lib/togls/helpers.rb +7 -0
- data/lib/togls/null_toggle.rb +16 -0
- data/lib/togls/rule.rb +12 -4
- data/lib/togls/rule_repository.rb +42 -0
- data/lib/togls/rule_repository_drivers/in_memory_driver.rb +17 -0
- data/lib/togls/rule_repository_drivers.rb +5 -0
- data/lib/togls/rules/boolean.rb +2 -6
- data/lib/togls/rules/group.rb +2 -6
- data/lib/togls/toggle.rb +33 -0
- data/lib/togls/toggle_repository.rb +71 -0
- data/lib/togls/toggle_repository_drivers/env_override_driver.rb +32 -0
- data/lib/togls/toggle_repository_drivers/in_memory_driver.rb +21 -0
- data/lib/togls/toggle_repository_drivers.rb +5 -0
- data/lib/togls/toggler.rb +24 -0
- data/lib/togls/version.rb +1 -1
- data/lib/togls.rb +21 -7
- data/togls.gemspec +3 -2
- metadata +35 -4
- data/lib/togls/feature_registry.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e723acd976b3387ca90f419b1bb628f6aba617ab
|
4
|
+
data.tar.gz: 528b2d7d8575d3c54e5fcec95a65c3f3d371d2a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03327c8c5b355d7297a6c56274567be505f15cc267862a85d7c32cbb602614298e5fb00869abebbe3c9c90a02d6b320330507645470c4a4a3bc42b4c82f756b6
|
7
|
+
data.tar.gz: daae0c8edfdac0b28a602200525679e9db989c5fc091a0aaf4077439d586ce449d44dbffdfc33ca3b8dbea6ea899f8c3d0694a55fe051bd5185fc735c0777f66
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.2.
|
1
|
+
2.2.3
|
data/.travis.yml
CHANGED
data/CODE_OF_CONDUCT.md
CHANGED
@@ -1,13 +1,30 @@
|
|
1
1
|
# Contributor Code of Conduct
|
2
2
|
|
3
|
-
As contributors and maintainers of this project, we pledge to respect
|
3
|
+
As contributors and maintainers of this project, we pledge to respect
|
4
|
+
all people who contribute through reporting issues, posting feature
|
5
|
+
requests, updating documentation, submitting pull requests or patches,
|
6
|
+
and other activities.
|
4
7
|
|
5
|
-
We are committed to making participation in this project a
|
8
|
+
We are committed to making participation in this project a
|
9
|
+
harassment-free experience for everyone, regardless of level of
|
10
|
+
experience, gender, gender identity and expression, sexual orientation,
|
11
|
+
disability, personal appearance, body size, race, age, or religion.
|
6
12
|
|
7
|
-
Examples of unacceptable behavior by participants include the use of
|
13
|
+
Examples of unacceptable behavior by participants include the use of
|
14
|
+
sexual language or imagery, derogatory comments or personal attacks,
|
15
|
+
trolling, public or private harassment, insults, or other unprofessional
|
16
|
+
conduct.
|
8
17
|
|
9
|
-
Project maintainers have the right and responsibility to remove, edit,
|
18
|
+
Project maintainers have the right and responsibility to remove, edit,
|
19
|
+
or reject comments, commits, code, wiki edits, issues, and other
|
20
|
+
contributions that are not aligned to this Code of Conduct. Project
|
21
|
+
maintainers who do not follow the Code of Conduct may be removed from
|
22
|
+
the project team.
|
10
23
|
|
11
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may
|
24
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may
|
25
|
+
be reported by opening an issue or contacting one or more of the project
|
26
|
+
maintainers.
|
12
27
|
|
13
|
-
This Code of Conduct is adapted from the [Contributor
|
28
|
+
This Code of Conduct is adapted from the [Contributor
|
29
|
+
Covenant](http:contributor-covenant.org), version 1.0.0, available at
|
30
|
+
[http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/ChangeLog.md
CHANGED
@@ -6,6 +6,13 @@ versions, as well as provide a rough history.
|
|
6
6
|
|
7
7
|
#### Next Release
|
8
8
|
|
9
|
+
#### v1.1.0
|
10
|
+
|
11
|
+
* Add `off?` method for asking if a defined feature is off
|
12
|
+
* Add feature toggle overrides
|
13
|
+
* Rearchitect to support concept of repositories and datastores allowing
|
14
|
+
further growth in the future
|
15
|
+
|
9
16
|
#### v1.0.0
|
10
17
|
|
11
18
|
* Require human readable description to define a feature toggle
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2015 Brian Miller
|
3
|
+
Copyright (c) 2015 Brian Miller, Andrew De Ponte, Ryan Hedges
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
[![Version](https://
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/togls.svg)](http://badge.fury.io/rb/togls)
|
2
2
|
[![Build Status](https://travis-ci.org/codebreakdown/togls.svg?branch=master)](https://travis-ci.org/codebreakdown/togls)
|
3
|
-
[![Code Climate](https://
|
4
|
-
[![
|
5
|
-
Coverage](https://img.shields.io/codeclimate/coverage/github/codebreakdown/togls.svg)](https://codeclimate.com/github/codebreakdown/togls)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/codebreakdown/togls/badges/gpa.svg)](https://codeclimate.com/github/codebreakdown/togls)
|
4
|
+
[![Test Coverage](https://codeclimate.com/github/codebreakdown/togls/badges/coverage.svg)](https://codeclimate.com/github/codebreakdown/togls/coverage)
|
6
5
|
[![Dependency Status](https://gemnasium.com/codebreakdown/togls.svg)](https://gemnasium.com/codebreakdown/togls)
|
7
6
|
|
8
7
|
# Togls
|
9
8
|
|
10
|
-
A lightweight feature toggle library
|
9
|
+
A lightweight, simple, and yet extremely flexible feature toggle library
|
10
|
+
for Ruby. It prides itself on being designed in such a manner that it is
|
11
|
+
extremely flexible and yet still provides a simple, clear, and
|
12
|
+
meaningful interface.
|
11
13
|
|
12
14
|
## Installation
|
13
15
|
|
@@ -27,32 +29,41 @@ Or install it yourself as:
|
|
27
29
|
|
28
30
|
## Basic Usage
|
29
31
|
|
30
|
-
|
32
|
+
At the core `togls` is primarily used by performing two distinct
|
33
|
+
actions.
|
34
|
+
|
35
|
+
1. Defining Feature Toggles
|
36
|
+
2. Evaluating Feature Toggles
|
31
37
|
|
32
38
|
### Defining Feature Toggles
|
33
39
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
it in `config/initializers/togls_features.rb`.
|
40
|
+
In order to use `togls`, you first have to define some feature toggles.
|
41
|
+
It is highly recommended that you define your feature toggles in their
|
42
|
+
own file. If you are using `togls` in a Rails project we recommend
|
43
|
+
putting it in `config/initializers/togls_features.rb`. If you are using
|
44
|
+
`togls` in a generic Ruby project you can locate it wherever you like.
|
45
|
+
However, the feature definitions **must** be loaded before they can be
|
46
|
+
evaluated.
|
47
|
+
|
48
|
+
The following, `config/initializers/togls_features.rb`, is an example of
|
49
|
+
how one would define some basic feature toggles.
|
38
50
|
|
39
51
|
```ruby
|
40
52
|
Togls.features do
|
41
53
|
# Set this feature to always be on
|
42
|
-
feature(:pop_up_login_form, "use
|
54
|
+
feature(:pop_up_login_form, "use pop up login instead of normal login").on
|
55
|
+
|
43
56
|
# Set this feature to always be off
|
44
57
|
feature(:send_follup_email, "send the follow up email").off
|
45
|
-
# Create a group rule
|
46
|
-
rule = Togls::Rules::Group.new(["user@email.com"])
|
47
|
-
feature(:new_contact_form, "use new contact form instead of old one").on(rule)
|
48
58
|
end
|
49
59
|
```
|
50
60
|
|
51
61
|
### Evaluating Feature Toggles
|
52
62
|
|
53
|
-
Once you have defined your feature toggles. The next thing you would
|
54
|
-
want to do is conditionally control something based on them. The
|
55
|
-
a few examples of how you would do this given the
|
63
|
+
Once you have defined your feature toggles. The next thing you would
|
64
|
+
likely want to do is conditionally control something based on them. The
|
65
|
+
following are a few examples of how you would do this given the feature
|
66
|
+
definitions provided above.
|
56
67
|
|
57
68
|
```ruby
|
58
69
|
if Togls.feature(:pop_up_login_form).on?
|
@@ -61,193 +72,147 @@ else
|
|
61
72
|
# Use normal non-pop up login form
|
62
73
|
end
|
63
74
|
|
75
|
+
...
|
76
|
+
|
64
77
|
if Togls.feature(:send_follup_email).on?
|
65
78
|
# send the follow up email
|
66
79
|
end
|
67
|
-
|
68
|
-
if Togls.feature(:new_contact_form).on?("user@email.com")
|
69
|
-
# Use new contact form
|
70
|
-
else
|
71
|
-
# Use old contact form
|
72
|
-
end
|
73
80
|
```
|
74
81
|
|
75
|
-
**Note:**
|
76
|
-
|
77
|
-
|
78
|
-
### Output Feature Toggles
|
79
|
-
|
80
|
-
One other use case that we support as part of the *Basic Usage* is outputing all
|
81
|
-
of the features in your system and their respective, **states** (`on`, `off`, `?` -
|
82
|
-
unkown due to *Complex Rule*), **key**, and **human readable descrption**.
|
83
|
-
|
84
|
-
We provide this functionality via a [rake](https://github.com/ruby/rake) task.
|
85
|
-
|
86
|
-
#### Load rake task
|
82
|
+
**Note:** Feature toggles that are evaluated but have **not** been
|
83
|
+
defined will default to **off**.
|
87
84
|
|
88
|
-
|
89
|
-
[rake](https://github.com/ruby/rake) file. This can be done in a number of
|
90
|
-
different ways.
|
85
|
+
### Override Feature Toggles
|
91
86
|
|
92
|
-
|
87
|
+
Toggles can be overriden using environment variables following the
|
88
|
+
naming scheme provided below. This is especially useful when wanting to
|
89
|
+
change state of toggles within your development environment, without
|
90
|
+
altering the codified default state.
|
93
91
|
|
94
|
-
|
95
|
-
`
|
92
|
+
The naming scheme for overriding toggles is simply the key of the
|
93
|
+
feature toggle in all caps with `TOGLS_` prefixed to it. For example if
|
94
|
+
we had the following feature toggle defined.
|
96
95
|
|
97
96
|
```ruby
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
**Note:** The features must be defined and loaded before calling this task or
|
103
|
-
it will error out informing you that you need to define your features first.
|
104
|
-
|
105
|
-
##### Load rake task in a Rails app
|
106
|
-
|
107
|
-
To use the it in a Rails app you can do so by adding the following to the
|
108
|
-
`Rakefile`.
|
109
|
-
|
110
|
-
```ruby
|
111
|
-
namespace :togls do
|
112
|
-
task :features => [:environment] do
|
113
|
-
end
|
97
|
+
Togls.features do
|
98
|
+
# Set this feature to always be on
|
99
|
+
feature(:pop_up_login_form, "use pop up login instead of normal login").on
|
114
100
|
end
|
115
|
-
|
116
|
-
spec = Gem::Specification.find_by_name 'togls'
|
117
|
-
load "#{spec.gem_dir}/lib/tasks/togls.rake"
|
118
101
|
```
|
119
102
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
environment
|
124
|
-
executed the `config/initializers/togls_features.rb` file is loaded which
|
125
|
-
defines the feature toggles.
|
103
|
+
We could override this by setting the value of the
|
104
|
+
`TOGLS_POP_UP_LOGIN_FORM` environment variable to `"false"`. If you want
|
105
|
+
to override a feature toggle to an on state you can set the
|
106
|
+
`TOGLS_POP_UP_LOGIN_FORM` environment variable to `"true"`.
|
126
107
|
|
127
|
-
|
108
|
+
### Toggle Features based on Group Membership
|
128
109
|
|
129
|
-
|
130
|
-
|
110
|
+
`togls` provides out of the box support for toggling features based on
|
111
|
+
group membership. This basically allows you to have a feature **on** for
|
112
|
+
members of a defined group and **off** for non-members. This can be
|
113
|
+
extremely useful if you want to enable features for a small alpha test
|
114
|
+
group for example.
|
131
115
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
116
|
+
**Note:** This is implemented using `togls` extremely robust [custom
|
117
|
+
rules](https://github.com/codebreakdown/togls/wiki/Custom-Rules) system.
|
118
|
+
The following example is just one of the many powerful things you can do
|
119
|
+
with `togls` and [custom
|
120
|
+
rules](https://github.com/codebreakdown/togls/wiki/Custom-Rules).
|
137
121
|
|
138
|
-
|
139
|
-
follows.
|
122
|
+
#### Defining Group based Feature Toggles
|
140
123
|
|
141
|
-
|
142
|
-
|
143
|
-
```
|
124
|
+
The following is an example definition of a feature that toggles on
|
125
|
+
based on group membership.
|
144
126
|
|
145
|
-
|
146
|
-
a
|
127
|
+
```ruby
|
128
|
+
# Create a group rule so the feature is on if the user is a member of
|
129
|
+
# the group.
|
130
|
+
alpha_testers = Togls::Rules::Group.new(["user1@email.com",
|
131
|
+
"user2@example.com"])
|
147
132
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
off - :test3 - test 3 feature
|
133
|
+
Togls.features do
|
134
|
+
feature(:new_contact_form, "use new contact form").on(alpha_testers)
|
135
|
+
end
|
152
136
|
```
|
153
137
|
|
154
|
-
|
138
|
+
The above is really broken down into two steps.
|
155
139
|
|
156
|
-
|
157
|
-
|
140
|
+
1. Construct the
|
141
|
+
[rule](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference)
|
142
|
+
you want to use
|
143
|
+
2. Define the feature toggle and pass the
|
144
|
+
[rule](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference)
|
145
|
+
to its `on()` method
|
158
146
|
|
159
|
-
|
147
|
+
In the above example we construct an instance of the
|
148
|
+
[Togls::Rules::Group](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference#toglsrulesgroup)
|
149
|
+
rule, passing it the list of alpha tester email addresses.
|
150
|
+
[Togls::Rules::Group](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference#toglsrulesgroup)
|
151
|
+
is a rule that `togls` provides to make your life a bit simpler.
|
160
152
|
|
161
|
-
|
162
|
-
|
163
|
-
like and use them to control their feature toggles. For example, you could
|
164
|
-
use them to do A/B testing, define alpha test group, give a percentage of a
|
165
|
-
user base a feature, etc.
|
153
|
+
Then we define a feature, `:new_contact_form`, that will be on for
|
154
|
+
alpha testers and off for people that don't fall within that group.
|
166
155
|
|
167
|
-
|
156
|
+
#### Evaluating Group based Feature Toggles
|
168
157
|
|
169
|
-
|
170
|
-
|
171
|
-
|
158
|
+
Group based rules are evaluated by using the `on?` method. However, in
|
159
|
+
this case we have to pass the `on?` method the `target`. The `target` in
|
160
|
+
this case is the email address of the current user. The target is the
|
161
|
+
identifier that you want to check if it belongs to the group or not.
|
172
162
|
|
173
163
|
```ruby
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
feature(:only_gmail_users).on(gmail_rule)
|
164
|
+
if Togls.feature(:new_contact_form).on?(current_user.email)
|
165
|
+
# Use new contact form
|
166
|
+
else
|
167
|
+
# Use old contact form
|
179
168
|
end
|
180
169
|
```
|
181
170
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
`Togls::Rules` that abides by the following.
|
186
|
-
|
187
|
-
- inherits from `Togls::Rule` or a decendent of it
|
188
|
-
- has an instance method named `run` that takes either a default value
|
189
|
-
parameter of `target` or a required paramter of `target`.
|
190
|
-
|
191
|
-
```ruby
|
192
|
-
def run(target = 'foo')
|
193
|
-
...
|
194
|
-
end
|
195
|
-
|
196
|
-
# or
|
171
|
+
**Note:** In the above example I reference `current_user.email`. This is
|
172
|
+
not something that `togls` provides. It is simply a common way for
|
173
|
+
Ruby/Rails applications to implement access to the concept of the current user.
|
197
174
|
|
198
|
-
|
199
|
-
...
|
200
|
-
end
|
201
|
-
```
|
175
|
+
#### Groups of Anything
|
202
176
|
|
203
|
-
|
204
|
-
|
177
|
+
You could just as easily have used user ids instead of email addresses
|
178
|
+
in the example above and it would look something like the following:
|
205
179
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
Internally `Togls` uses these *Complex Rules* to provide functionality and
|
211
|
-
will evolve to contain more official rules over time. If you have a generic
|
212
|
-
*Complex Rule* you think should be part of `Togls` please make a pull request.
|
180
|
+
```ruby
|
181
|
+
# Create a group rule so the feature is on if the user is a member of
|
182
|
+
# the group.
|
183
|
+
alpha_testers = Togls::Rules::Group.new([1, 23, 42, 83])
|
213
184
|
|
214
|
-
|
215
|
-
|
185
|
+
Togls.features do
|
186
|
+
feature(:new_contact_form, "use new contact form").on(alpha_testers)
|
187
|
+
end
|
188
|
+
```
|
216
189
|
|
217
190
|
```ruby
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
@list = list
|
223
|
-
end
|
224
|
-
|
225
|
-
def run(target)
|
226
|
-
@list.include?(target)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
191
|
+
if Togls.feature(:new_contact_form).on?(current_user.id)
|
192
|
+
# Use new contact form
|
193
|
+
else
|
194
|
+
# Use old contact form
|
230
195
|
end
|
231
196
|
```
|
232
197
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
198
|
+
The key take away is that the
|
199
|
+
[Togls::Rules::Group](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference#toglsrulesgroup)
|
200
|
+
rule can be used to define any group you like.
|
201
|
+
|
202
|
+
## Advanced Usage
|
203
|
+
|
204
|
+
`togls` is capable of much, much more. We have strategically avoided
|
205
|
+
including details of advanced usage in the `README.md` as to not
|
206
|
+
overwhelm people on first impression. For more details on some of the
|
207
|
+
more advanced features feel free to check out our
|
208
|
+
[Wiki](https://github.com/codebreakdown/togls/wiki). Just a few of the
|
209
|
+
many things it contains are [Provided Rules
|
210
|
+
Reference](https://github.com/codebreakdown/togls/wiki/Provided-Rules-Reference),
|
211
|
+
[Custom
|
212
|
+
Rules](https://github.com/codebreakdown/togls/wiki/Custom-Rules),
|
213
|
+
[Review Feature
|
214
|
+
Toggles](https://github.com/codebreakdown/togls/wiki/Review-Feature-Toggles),
|
215
|
+
etc.
|
251
216
|
|
252
217
|
## Development
|
253
218
|
|
Binary file
|
data/lib/tasks/togls.rake
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
namespace :togls do
|
2
2
|
desc "Output all features including status (on, off, ? - unknown due to complex rule), key, description"
|
3
3
|
task :features do
|
4
|
-
Togls.features.each do |
|
5
|
-
puts
|
4
|
+
Togls.features.each do |toggle|
|
5
|
+
puts toggle.to_s
|
6
6
|
end
|
7
7
|
end
|
8
8
|
end
|
data/lib/togls/errors.rb
CHANGED
data/lib/togls/feature.rb
CHANGED
@@ -3,36 +3,12 @@ module Togls
|
|
3
3
|
attr_reader :key, :description
|
4
4
|
|
5
5
|
def initialize(key, description)
|
6
|
-
@key = key
|
6
|
+
@key = key.to_s
|
7
7
|
@description = description
|
8
|
-
off
|
9
8
|
end
|
10
9
|
|
11
|
-
def
|
12
|
-
|
13
|
-
rule = Togls::Rules::Boolean.new(true)
|
14
|
-
end
|
15
|
-
@rule = rule
|
16
|
-
self
|
17
|
-
end
|
18
|
-
|
19
|
-
def off
|
20
|
-
@rule = Togls::Rules::Boolean.new(false)
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
def on?(target = nil)
|
25
|
-
@rule.run(target)
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_s
|
29
|
-
if @rule.is_a?(Togls::Rules::Boolean)
|
30
|
-
display_value = @rule.run ? ' on' : 'off'
|
31
|
-
else
|
32
|
-
display_value = ' ?'
|
33
|
-
end
|
34
|
-
|
35
|
-
"#{display_value} - #{@key.inspect} - #{@description}"
|
10
|
+
def id
|
11
|
+
@key
|
36
12
|
end
|
37
13
|
end
|
38
14
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Togls
|
2
|
+
class FeatureRepository
|
3
|
+
def initialize(drivers)
|
4
|
+
if !drivers.is_a?(Array)
|
5
|
+
raise Togls::InvalidDriver.new("FeatureRepository requires a valid driver")
|
6
|
+
end
|
7
|
+
if drivers.empty?
|
8
|
+
raise Togls::MissingDriver.new("FeatureRepository requires a driver")
|
9
|
+
end
|
10
|
+
@drivers = drivers
|
11
|
+
end
|
12
|
+
|
13
|
+
def store(feature)
|
14
|
+
feature_data = extract_feature_data(feature)
|
15
|
+
@drivers.each do |driver|
|
16
|
+
driver.store(feature.id, feature_data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_feature_data(feature)
|
21
|
+
{ "key" => feature.key, "description" => feature.description }
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_feature_data(id)
|
25
|
+
feature_data = nil
|
26
|
+
@drivers.reverse.each do |driver|
|
27
|
+
feature_data = driver.get(id)
|
28
|
+
break if feature_data
|
29
|
+
end
|
30
|
+
feature_data
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(feature_id)
|
34
|
+
feature_data = fetch_feature_data(feature_id)
|
35
|
+
reconstitute_feature(feature_data)
|
36
|
+
end
|
37
|
+
|
38
|
+
def reconstitute_feature(feature_data)
|
39
|
+
Togls::Feature.new(feature_data["key"],
|
40
|
+
feature_data["description"])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Togls
|
2
|
+
module FeatureRepositoryDrivers
|
3
|
+
class InMemoryDriver
|
4
|
+
def initialize
|
5
|
+
@features = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def store(feature_id, feature_data)
|
9
|
+
@features[feature_id] = feature_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(feature_id)
|
13
|
+
@features[feature_id]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Togls
|
2
|
+
class FeatureToggleRegistry
|
3
|
+
def initialize
|
4
|
+
@toggle_repository_drivers = [
|
5
|
+
Togls::ToggleRepositoryDrivers::InMemoryDriver.new,
|
6
|
+
Togls::ToggleRepositoryDrivers::EnvOverrideDriver.new]
|
7
|
+
@feature_repository_drivers =
|
8
|
+
[Togls::FeatureRepositoryDrivers::InMemoryDriver.new]
|
9
|
+
@rule_repository_drivers =
|
10
|
+
[Togls::RuleRepositoryDrivers::InMemoryDriver.new]
|
11
|
+
@feature_repository = Togls::FeatureRepository.new(@feature_repository_drivers)
|
12
|
+
@rule_repository = Togls::RuleRepository.new(@rule_repository_drivers)
|
13
|
+
@toggle_repository = Togls::ToggleRepository.new(@toggle_repository_drivers,
|
14
|
+
@feature_repository, @rule_repository)
|
15
|
+
@boolean_true_rule = Togls::Rules::Boolean.new(true)
|
16
|
+
@boolean_false_rule = Togls::Rules::Boolean.new(false)
|
17
|
+
@rule_repository.store(@boolean_false_rule)
|
18
|
+
@rule_repository.store(@boolean_true_rule)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.create(&feature_toggles)
|
22
|
+
feature_toggle_registry = self.new
|
23
|
+
feature_toggle_registry.instance_eval(&feature_toggles)
|
24
|
+
return feature_toggle_registry
|
25
|
+
end
|
26
|
+
|
27
|
+
def feature(key, desc)
|
28
|
+
feature = Togls::Feature.new(key, desc)
|
29
|
+
toggle = Togls::Toggle.new(feature)
|
30
|
+
@toggle_repository.store(toggle)
|
31
|
+
Togls::Toggler.new(@toggle_repository, toggle)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(key)
|
35
|
+
toggle = @toggle_repository.get(key.to_s)
|
36
|
+
if toggle.is_a?(Togls::NullToggle)
|
37
|
+
Togls.logger.warn("Feature identified by '#{key}' has not been defined")
|
38
|
+
end
|
39
|
+
return toggle
|
40
|
+
end
|
41
|
+
|
42
|
+
def registry
|
43
|
+
@toggle_repository.all
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/togls/rule.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
module Togls
|
2
2
|
class Rule
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :data
|
4
|
+
|
5
|
+
def initialize(data=nil)
|
6
|
+
@data = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(key, target = nil)
|
10
|
+
raise Togls::NotImplemented.new(
|
11
|
+
"Rule's #run method must be implemented"
|
12
|
+
)
|
5
13
|
end
|
6
14
|
|
7
|
-
def
|
8
|
-
|
15
|
+
def id
|
16
|
+
Togls::Helpers.sha1(self.class, @data)
|
9
17
|
end
|
10
18
|
end
|
11
19
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Togls
|
2
|
+
class RuleRepository
|
3
|
+
def initialize(drivers)
|
4
|
+
if !drivers.is_a?(Array)
|
5
|
+
raise Togls::InvalidDriver.new("RuleRepository requires a valid driver")
|
6
|
+
end
|
7
|
+
if drivers.empty?
|
8
|
+
raise Togls::MissingDriver.new("RuleRepository requires a driver")
|
9
|
+
end
|
10
|
+
@drivers = drivers
|
11
|
+
end
|
12
|
+
|
13
|
+
def store(rule)
|
14
|
+
rule_data = extract_storage_payload(rule)
|
15
|
+
@drivers.each do |driver|
|
16
|
+
driver.store(rule.id, rule_data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_storage_payload(rule)
|
21
|
+
return { "klass" => rule.class, "data" => rule.data }
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_rule_data(id)
|
25
|
+
rule_data = nil
|
26
|
+
@drivers.reverse.each do |driver|
|
27
|
+
rule_data = driver.get(id)
|
28
|
+
break if rule_data
|
29
|
+
end
|
30
|
+
rule_data
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(rule_id)
|
34
|
+
rule_data = fetch_rule_data(rule_id)
|
35
|
+
reconstitute_rule(rule_data)
|
36
|
+
end
|
37
|
+
|
38
|
+
def reconstitute_rule(rule_data)
|
39
|
+
rule_data["klass"].new(rule_data["data"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/togls/rules/boolean.rb
CHANGED
data/lib/togls/rules/group.rb
CHANGED
data/lib/togls/toggle.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Togls
|
2
|
+
class Toggle
|
3
|
+
attr_reader :feature
|
4
|
+
attr_accessor :rule
|
5
|
+
|
6
|
+
def initialize(feature)
|
7
|
+
@feature = feature
|
8
|
+
@rule = Togls::Rules::Boolean.new(false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def id
|
12
|
+
@feature.id
|
13
|
+
end
|
14
|
+
|
15
|
+
def on?(target = nil)
|
16
|
+
@rule.run(@feature.key, target)
|
17
|
+
end
|
18
|
+
|
19
|
+
def off?(target = nil)
|
20
|
+
return !@rule.run(@feature.key, target)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
if @rule.is_a?(Togls::Rules::Boolean)
|
25
|
+
display_value = @rule.run(@feature.key) ? ' on' : 'off'
|
26
|
+
else
|
27
|
+
display_value = ' ?'
|
28
|
+
end
|
29
|
+
|
30
|
+
"#{display_value} - #{@feature.key} - #{@feature.description}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Togls
|
2
|
+
class ToggleRepository
|
3
|
+
def initialize(drivers, feature_repository, rule_repository)
|
4
|
+
if !drivers.is_a?(Array)
|
5
|
+
raise Togls::InvalidDriver.new("ToggleRepository requires a valid driver")
|
6
|
+
end
|
7
|
+
if drivers.empty?
|
8
|
+
raise Togls::MissingDriver.new("ToggleRepository requires a driver")
|
9
|
+
end
|
10
|
+
@drivers = drivers
|
11
|
+
@feature_repository = feature_repository
|
12
|
+
@rule_repository = rule_repository
|
13
|
+
end
|
14
|
+
|
15
|
+
def store(toggle)
|
16
|
+
@feature_repository.store(toggle.feature)
|
17
|
+
@rule_repository.store(toggle.rule)
|
18
|
+
payload = extract_storage_payload(toggle)
|
19
|
+
|
20
|
+
@drivers.each do |driver|
|
21
|
+
driver.store(toggle.id, payload)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def extract_storage_payload(toggle)
|
26
|
+
{ "feature_id" => toggle.feature.id, "rule_id" => toggle.rule.id }
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(id)
|
30
|
+
toggle_data = fetch_toggle_data(id)
|
31
|
+
if toggle_data
|
32
|
+
return reconstitute_toggle(toggle_data)
|
33
|
+
else
|
34
|
+
return Togls::NullToggle.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def reconstitute_toggle(toggle_data)
|
39
|
+
feature = @feature_repository.get(toggle_data["feature_id"])
|
40
|
+
rule = @rule_repository.get(toggle_data["rule_id"])
|
41
|
+
toggle = Togls::Toggle.new(feature)
|
42
|
+
toggle.rule = rule
|
43
|
+
return toggle
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch_toggle_data(id)
|
47
|
+
toggle_data = nil
|
48
|
+
@drivers.reverse.each do |driver|
|
49
|
+
toggle_data = driver.get(id)
|
50
|
+
break if toggle_data
|
51
|
+
end
|
52
|
+
toggle_data
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_all_toggle_data
|
56
|
+
toggle_data_collection = {}
|
57
|
+
@drivers.each do |driver|
|
58
|
+
toggle_data_collection.merge!(driver.all)
|
59
|
+
end
|
60
|
+
toggle_data_collection
|
61
|
+
end
|
62
|
+
|
63
|
+
def all
|
64
|
+
toggle_data_collection = fetch_all_toggle_data.values
|
65
|
+
|
66
|
+
toggle_data_collection.map do |toggle_data|
|
67
|
+
reconstitute_toggle(toggle_data)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Togls
|
2
|
+
module ToggleRepositoryDrivers
|
3
|
+
class EnvOverrideDriver
|
4
|
+
def store(toggle_id, toggle_data)
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(toggle_id)
|
8
|
+
if ENV[toggle_env_key(toggle_id)]
|
9
|
+
if ENV[toggle_env_key(toggle_id)] == "true"
|
10
|
+
return { "feature_id" => toggle_id,
|
11
|
+
"rule_id" => Togls::Helpers.sha1(Togls::Rules::Boolean, true) }
|
12
|
+
else
|
13
|
+
return { "feature_id" => toggle_id,
|
14
|
+
"rule_id" => Togls::Helpers.sha1(Togls::Rules::Boolean, false) }
|
15
|
+
end
|
16
|
+
else
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
return {}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def toggle_env_key(toggle_id)
|
28
|
+
return "TOGLS_#{toggle_id.to_s.upcase}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Togls
|
2
|
+
module ToggleRepositoryDrivers
|
3
|
+
class InMemoryDriver
|
4
|
+
def initialize
|
5
|
+
@toggles = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def store(toggle_id, toggle_data)
|
9
|
+
@toggles[toggle_id] = toggle_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(toggle_id)
|
13
|
+
@toggles[toggle_id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
@toggles
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Togls
|
2
|
+
class Toggler
|
3
|
+
def initialize(toggle_repository, toggle)
|
4
|
+
@toggle_repository = toggle_repository
|
5
|
+
@toggle = toggle
|
6
|
+
end
|
7
|
+
|
8
|
+
def on(rule = nil)
|
9
|
+
if rule.nil?
|
10
|
+
rule = Togls::Rules::Boolean.new(true)
|
11
|
+
end
|
12
|
+
@toggle.rule = rule
|
13
|
+
@toggle_repository.store(@toggle)
|
14
|
+
@toggle
|
15
|
+
end
|
16
|
+
|
17
|
+
def off
|
18
|
+
rule = Togls::Rules::Boolean.new(false)
|
19
|
+
@toggle.rule = rule
|
20
|
+
@toggle_repository.store(@toggle)
|
21
|
+
@toggle
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/togls/version.rb
CHANGED
data/lib/togls.rb
CHANGED
@@ -1,26 +1,40 @@
|
|
1
1
|
require "togls/version"
|
2
2
|
require "togls/errors"
|
3
|
-
require "togls/
|
3
|
+
require "togls/helpers"
|
4
|
+
require "togls/toggle_repository_drivers"
|
5
|
+
require "togls/toggle_repository_drivers/in_memory_driver"
|
6
|
+
require "togls/toggle_repository_drivers/env_override_driver"
|
7
|
+
require "togls/feature_repository_drivers"
|
8
|
+
require "togls/feature_repository_drivers/in_memory_driver"
|
9
|
+
require "togls/rule_repository_drivers"
|
10
|
+
require "togls/rule_repository_drivers/in_memory_driver"
|
11
|
+
require "togls/toggler"
|
12
|
+
require "togls/feature_toggle_registry"
|
13
|
+
require "togls/feature_repository"
|
14
|
+
require "togls/rule_repository"
|
15
|
+
require "togls/toggle_repository"
|
4
16
|
require "togls/feature"
|
17
|
+
require "togls/toggle"
|
18
|
+
require "togls/null_toggle"
|
5
19
|
require "togls/rule"
|
6
20
|
require "togls/rules"
|
7
21
|
require "logger"
|
8
22
|
|
9
23
|
module Togls
|
10
|
-
def self.features(&
|
11
|
-
if !
|
12
|
-
@
|
24
|
+
def self.features(&feature_toggles)
|
25
|
+
if !feature_toggles.nil?
|
26
|
+
@feature_toggle_registry = FeatureToggleRegistry.create(&feature_toggles)
|
13
27
|
else
|
14
|
-
if @
|
28
|
+
if @feature_toggle_registry.nil?
|
15
29
|
raise Togls::NoFeaturesError, "Need to define features before you can get them"
|
16
30
|
else
|
17
|
-
@
|
31
|
+
@feature_toggle_registry.registry
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
21
35
|
|
22
36
|
def self.feature(key)
|
23
|
-
@
|
37
|
+
return @feature_toggle_registry.get(key)
|
24
38
|
end
|
25
39
|
|
26
40
|
def self.logger
|
data/togls.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'togls/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "togls"
|
8
8
|
spec.version = Togls::VERSION
|
9
|
-
spec.authors = ["Brian Miller", "Andrew DePonte"]
|
10
|
-
spec.email = ["brimil01@gmail.com", "cyphactor@gmail.com"]
|
9
|
+
spec.authors = ["Brian Miller", "Andrew DePonte", "Ryan Hedges"]
|
10
|
+
spec.email = ["brimil01@gmail.com", "cyphactor@gmail.com", "ryanhedges@gmail.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{An ultra light weight yet ridiculously powerful ruby feature toggle gem.}
|
13
13
|
spec.description = %q{An ultra light weight yet ridiculously powerful ruby feature toggle gem that supports the concept of release toggles and business toggles.}
|
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.9"
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.2"
|
25
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
25
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: togls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Miller
|
8
8
|
- Andrew DePonte
|
9
|
+
- Ryan Hedges
|
9
10
|
autorequire:
|
10
11
|
bindir: exe
|
11
12
|
cert_chain: []
|
12
|
-
date: 2015-
|
13
|
+
date: 2015-11-17 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -53,11 +54,26 @@ dependencies:
|
|
53
54
|
- - "~>"
|
54
55
|
- !ruby/object:Gem::Version
|
55
56
|
version: '3.2'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: pry
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.10'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0.10'
|
56
71
|
description: An ultra light weight yet ridiculously powerful ruby feature toggle gem
|
57
72
|
that supports the concept of release toggles and business toggles.
|
58
73
|
email:
|
59
74
|
- brimil01@gmail.com
|
60
75
|
- cyphactor@gmail.com
|
76
|
+
- ryanhedges@gmail.com
|
61
77
|
executables: []
|
62
78
|
extensions: []
|
63
79
|
extra_rdoc_files: []
|
@@ -75,15 +91,30 @@ files:
|
|
75
91
|
- Rakefile
|
76
92
|
- bin/console
|
77
93
|
- bin/setup
|
94
|
+
- contributing/togls_architecture.monopic
|
78
95
|
- lib/tasks/togls.rake
|
79
96
|
- lib/togls.rb
|
80
97
|
- lib/togls/errors.rb
|
81
98
|
- lib/togls/feature.rb
|
82
|
-
- lib/togls/
|
99
|
+
- lib/togls/feature_repository.rb
|
100
|
+
- lib/togls/feature_repository_drivers.rb
|
101
|
+
- lib/togls/feature_repository_drivers/in_memory_driver.rb
|
102
|
+
- lib/togls/feature_toggle_registry.rb
|
103
|
+
- lib/togls/helpers.rb
|
104
|
+
- lib/togls/null_toggle.rb
|
83
105
|
- lib/togls/rule.rb
|
106
|
+
- lib/togls/rule_repository.rb
|
107
|
+
- lib/togls/rule_repository_drivers.rb
|
108
|
+
- lib/togls/rule_repository_drivers/in_memory_driver.rb
|
84
109
|
- lib/togls/rules.rb
|
85
110
|
- lib/togls/rules/boolean.rb
|
86
111
|
- lib/togls/rules/group.rb
|
112
|
+
- lib/togls/toggle.rb
|
113
|
+
- lib/togls/toggle_repository.rb
|
114
|
+
- lib/togls/toggle_repository_drivers.rb
|
115
|
+
- lib/togls/toggle_repository_drivers/env_override_driver.rb
|
116
|
+
- lib/togls/toggle_repository_drivers/in_memory_driver.rb
|
117
|
+
- lib/togls/toggler.rb
|
87
118
|
- lib/togls/version.rb
|
88
119
|
- togls.gemspec
|
89
120
|
homepage: http://github.com/codebreakdown/togls
|
@@ -106,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
137
|
version: '0'
|
107
138
|
requirements: []
|
108
139
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.4.5
|
140
|
+
rubygems_version: 2.4.5.1
|
110
141
|
signing_key:
|
111
142
|
specification_version: 4
|
112
143
|
summary: An ultra light weight yet ridiculously powerful ruby feature toggle gem.
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Togls
|
2
|
-
class FeatureRegistry
|
3
|
-
def initialize
|
4
|
-
@registry = {}
|
5
|
-
@default_feature = Feature.new(:default, "the official default feature").tap {|f| f.on(Rule.new { false }) }
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.create(&features)
|
9
|
-
registry = self.new
|
10
|
-
registry.instance_eval(&features)
|
11
|
-
registry
|
12
|
-
end
|
13
|
-
|
14
|
-
def feature(tag, desc)
|
15
|
-
@registry[tag.to_sym] = Feature.new(tag.to_sym, desc)
|
16
|
-
end
|
17
|
-
|
18
|
-
def get(key)
|
19
|
-
@registry.fetch(key) do |key|
|
20
|
-
Togls.logger.warn("Feature identified by #{key} has not been defined")
|
21
|
-
@default_feature
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def registry
|
26
|
-
@registry
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|