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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f956666596995d557aa56048b785db8f490f73dc
4
- data.tar.gz: ffa83b7c726ef586d20c169c6cf3d3189ee8207c
3
+ metadata.gz: e723acd976b3387ca90f419b1bb628f6aba617ab
4
+ data.tar.gz: 528b2d7d8575d3c54e5fcec95a65c3f3d371d2a8
5
5
  SHA512:
6
- metadata.gz: 50b448deb27e15b7815cafa0e02d366a01643038ca1a689695547e48234f0a2aafe5161cea5a9c9461d7b179022dd2cd22d8b8080756f3c62d760220a92e20f9
7
- data.tar.gz: 00963574a1dbef269a279ae8b42294a11042a194f80b366f38b9136a5d23e4eba23a634d16705b1f8990ad9c4f56821db4bb754b8d3d05aba9ded8d8da6ba6a9
6
+ metadata.gz: 03327c8c5b355d7297a6c56274567be505f15cc267862a85d7c32cbb602614298e5fb00869abebbe3c9c90a02d6b320330507645470c4a4a3bc42b4c82f756b6
7
+ data.tar.gz: daae0c8edfdac0b28a602200525679e9db989c5fc091a0aaf4077439d586ce449d44dbffdfc33ca3b8dbea6ea899f8c3d0694a55fe051bd5185fc735c0777f66
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2.2
1
+ 2.2.3
data/.travis.yml CHANGED
@@ -1,3 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.2.3
3
4
  - 2.2.2
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 all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
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 harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
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 sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
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, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
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 be reported by opening an issue or contacting one or more of the project maintainers.
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 Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
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
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in togl.gemspec
4
4
  gemspec
5
5
 
6
- gem "codeclimate-test-reporter", group: :test, require: nil
6
+ gem "codeclimate-test-reporter", "~> 0.4.8", group: :test, require: nil
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://img.shields.io/gem/v/togls.svg)](https://rubygems.org/gems/togls)
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://img.shields.io/codeclimate/github/codebreakdown/togls.svg)](https://codeclimate.com/github/codebreakdown/togls)
4
- [![Code
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
- The basic usage of `Togls` is outlined below.
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
- The first thing to do to use `Togls` is to define your feature toggles. The
35
- following is an example of how you might define your feature toggles. It is
36
- recommended this live in its own file. In Rails projects we recommend putting
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 the pop up login instead of normal login").on
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 likely
54
- want to do is conditionally control something based on them. The following are
55
- a few examples of how you would do this given the above.
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:** The default behaviour for any feature that has not been defined that
76
- is accessed is to default to false.
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
- To use it you must first load the provided
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
- ##### Load rake task in another gem
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
- To use it in another gem you can use the following in the second gem's
95
- `Rakefile`.
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
- spec = Gem::Specification.find_by_name 'togls'
99
- load "#{spec.gem_dir}/lib/tasks/togls.rake"
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
- **Note:** The first hunk where it defines an empty `togls:features` tasks is
121
- important in Rails because it takes advantage of
122
- [rake](https://github.com/ruby/rake) task stacking and calls out the Rails
123
- environment as a dependency. That way before the `togls:feature` task is
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
- #### Verify rake task loaded
108
+ ### Toggle Features based on Group Membership
128
109
 
129
- To verify that the rake task is loaded you can run `rake -T` and you should
130
- see something similar to the following in the output.
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
- ```text
133
- rake togls:features # Output all features including status (on, off, ? - unknown due to complex rule), ke...
134
- ```
135
-
136
- #### Run the rake task
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
- Once you have verified the task is loaded and available you can run it as
139
- follows.
122
+ #### Defining Group based Feature Toggles
140
123
 
141
- ```shell
142
- rake togls:features
143
- ```
124
+ The following is an example definition of a feature that toggles on
125
+ based on group membership.
144
126
 
145
- The following is an example of what the output might look like if you defined
146
- a few test features.
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
- ```text
149
- on - :test1 - test 1 feature
150
- off - :test2 - test 2 feature
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
- ## Advanced Usage
138
+ The above is really broken down into two steps.
155
139
 
156
- Below is a breakdown of some of the more advanced features of `Togls` which
157
- aren't necessary in order to use it for basic feature toggles.
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
- ### Custom Rules
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
- `Togls` is specifically architected on top of a generic concept of a
162
- `Togls::Rule`. This empowers the users to define any custom rules they would
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
- ### Simple Rules
156
+ #### Evaluating Group based Feature Toggles
168
157
 
169
- A simple rule can be defined by creating a rule object and passing a block. In
170
- the following example any feature using the `gmail_rule` would only be on if
171
- the given `target` contained `gmail.com` at the end of the `target`.
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
- # Only allow users with email addresses at gmail.com
175
- gmail_rule = Togls::Rule.new { |target| target =~ /gmail.com$/ }
176
-
177
- Togls.features do
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
- ### Complex Rules
183
-
184
- To implement a more complex rule, a new rule object can be defined under
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
- def run(target)
199
- ...
200
- end
201
- ```
175
+ #### Groups of Anything
202
176
 
203
- - has the instance method named `run` return a boolean value identifying if
204
- that feature should be on(`true`)/off(`false`) given the `target`.
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
- Thats it!
207
-
208
- #### Example Complex Rule
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
- A prime example of a *Complex Rule* provided by `Togls` is the
215
- `Togls::Rules::Group` rule. You can see it below.
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
- module Togls
219
- module Rules
220
- class Group < Rule
221
- def initialize(list)
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
- Lets take a closer look at exactly what is going on here.
234
-
235
- - it is inheriting from the `Togls::Rule` class which meets one of the minimum
236
- requirements for a rule.
237
- - its defines constructor takes an array identifiers and stores them. These
238
- identifiers could be id numbers, email address, etc. **Note:** This
239
- constructor is completely different than that of `Togls::Rule`. This
240
- is fine because it is **not** a requirements that the constructor match.
241
- - its `run` method returns a boolean value identifying if the feature should
242
- be on(`true`)/off(`false`) for the given `target`. It does so by identifying
243
- if the array passed in at construction time `include?` the given `target`.
244
- This meets one of the minimum requirement for a rule.
245
- - its `run` method signature requires a `target`. This meets a minimum
246
- requirement for a rule as well. It also makes sense in the case of group
247
- based rule as it has to have something to compare against the group.
248
-
249
- *Complex Rules* are a simple yet extremely power concept that you shouldn't
250
- hesitate to use.
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
 
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 |key, feature|
5
- puts feature.to_s
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
@@ -1,3 +1,6 @@
1
1
  module Togls
2
2
  class NoFeaturesError < StandardError; end
3
+ class MissingDriver < StandardError; end
4
+ class InvalidDriver < StandardError; end
5
+ class NotImplemented < StandardError; end
3
6
  end
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 on(rule = nil)
12
- if rule.nil?
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,4 @@
1
+ module Togls
2
+ module FeatureRepositoryDrivers
3
+ end
4
+ 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
@@ -0,0 +1,7 @@
1
+ module Togls
2
+ module Helpers
3
+ def self.sha1(klass, data)
4
+ Digest::SHA1.hexdigest("#{klass}:#{data.to_s}")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Togls
2
+ class NullToggle < Togls::Toggle
3
+ def initialize
4
+ feature = Togls::Feature.new("null", "the official null feature")
5
+ super(feature)
6
+ end
7
+
8
+ def on
9
+ self
10
+ end
11
+
12
+ def off
13
+ self
14
+ end
15
+ end
16
+ end
data/lib/togls/rule.rb CHANGED
@@ -1,11 +1,19 @@
1
1
  module Togls
2
2
  class Rule
3
- def initialize(&rule)
4
- @definition = rule
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 run(target = nil)
8
- @definition.call(target)
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
@@ -0,0 +1,17 @@
1
+ module Togls
2
+ module RuleRepositoryDrivers
3
+ class InMemoryDriver
4
+ def initialize
5
+ @rules = {}
6
+ end
7
+
8
+ def store(rule_id, rule_data)
9
+ @rules[rule_id] = rule_data
10
+ end
11
+
12
+ def get(rule_id)
13
+ @rules[rule_id]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module Togls
2
+ module RuleRepositoryDrivers
3
+
4
+ end
5
+ end
@@ -1,12 +1,8 @@
1
1
  module Togls
2
2
  module Rules
3
3
  class Boolean < Rule
4
- def initialize(bool)
5
- @bool = bool
6
- end
7
-
8
- def run(target = nil)
9
- return @bool
4
+ def run(key, target = nil)
5
+ return @data
10
6
  end
11
7
  end
12
8
  end
@@ -1,12 +1,8 @@
1
1
  module Togls
2
2
  module Rules
3
3
  class Group < Rule
4
- def initialize(list)
5
- @list = list
6
- end
7
-
8
- def run(target)
9
- @list.include?(target)
4
+ def run(key, target)
5
+ @data.include?(target)
10
6
  end
11
7
  end
12
8
  end
@@ -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,5 @@
1
+ module Togls
2
+ module ToggleRepositoryDrivers
3
+
4
+ end
5
+ 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
@@ -1,3 +1,3 @@
1
1
  module Togls
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/togls.rb CHANGED
@@ -1,26 +1,40 @@
1
1
  require "togls/version"
2
2
  require "togls/errors"
3
- require "togls/feature_registry"
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(&features)
11
- if !features.nil?
12
- @feature_registry = FeatureRegistry.create(&features)
24
+ def self.features(&feature_toggles)
25
+ if !feature_toggles.nil?
26
+ @feature_toggle_registry = FeatureToggleRegistry.create(&feature_toggles)
13
27
  else
14
- if @feature_registry.nil?
28
+ if @feature_toggle_registry.nil?
15
29
  raise Togls::NoFeaturesError, "Need to define features before you can get them"
16
30
  else
17
- @feature_registry.registry
31
+ @feature_toggle_registry.registry
18
32
  end
19
33
  end
20
34
  end
21
35
 
22
36
  def self.feature(key)
23
- @feature_registry.get(key)
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.0.0
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-05-19 00:00:00.000000000 Z
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/feature_registry.rb
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