togls 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29cf1b0568aea63a9f302b01727e2b03e2f6b022
4
- data.tar.gz: 7b109336122ffd30f15621b8b769d3ad0556bd34
3
+ metadata.gz: f956666596995d557aa56048b785db8f490f73dc
4
+ data.tar.gz: ffa83b7c726ef586d20c169c6cf3d3189ee8207c
5
5
  SHA512:
6
- metadata.gz: 76ed9cf6f4d3b726902a8332fb16b57a1f4355cd3004cf0eca1f73228a9e4c304a89c967ebf157ebe5b3cc5003809223ed5dfd2577eb78dbc9197d4567bd7f05
7
- data.tar.gz: 466bb270590c5a5000340ce3585da8f51195ae9016f79b1200d09dcc2f2f620a3809ac4168b1f1b9b51f1dff6ca6f5911b15e4a3e4fd5303d0e35742db4357c9
6
+ metadata.gz: 50b448deb27e15b7815cafa0e02d366a01643038ca1a689695547e48234f0a2aafe5161cea5a9c9461d7b179022dd2cd22d8b8080756f3c62d760220a92e20f9
7
+ data.tar.gz: 00963574a1dbef269a279ae8b42294a11042a194f80b366f38b9136a5d23e4eba23a634d16705b1f8990ad9c4f56821db4bb754b8d3d05aba9ded8d8da6ba6a9
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
data/ChangeLog.md CHANGED
@@ -6,6 +6,12 @@ versions, as well as provide a rough history.
6
6
 
7
7
  #### Next Release
8
8
 
9
+ #### v1.0.0
10
+
11
+ * Require human readable description to define a feature toggle
12
+ * Add rake task that outputs all the feature toggles states (on, off, ? -
13
+ unknown due to Compex Rule), keys, and human readable descritpions
14
+
9
15
  #### v0.1.0
10
16
 
11
17
  * Add concept of Groups as a provided rule
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Version](https://img.shields.io/gem/v/togls.svg)](https://rubygems.org/gems/togls)
1
2
  [![Build Status](https://travis-ci.org/codebreakdown/togls.svg?branch=master)](https://travis-ci.org/codebreakdown/togls)
2
3
  [![Code Climate](https://img.shields.io/codeclimate/github/codebreakdown/togls.svg)](https://codeclimate.com/github/codebreakdown/togls)
3
4
  [![Code
@@ -24,35 +25,150 @@ Or install it yourself as:
24
25
 
25
26
  $ gem install togls
26
27
 
27
- ## Usage
28
+ ## Basic Usage
28
29
 
29
- ### Setup
30
+ The basic usage of `Togls` is outlined below.
30
31
 
31
- The default behaviour for any feature that has not been defined that is accessed is to default to false.
32
+ ### Defining Feature Toggles
33
+
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`.
32
38
 
33
39
  ```ruby
34
40
  Togls.features do
35
41
  # Set this feature to always be on
36
- feature(:pop_up_login_form).on
42
+ feature(:pop_up_login_form, "use the pop up login instead of normal login").on
37
43
  # Set this feature to always be off
38
- feature(:send_follup_email).off
44
+ feature(:send_follup_email, "send the follow up email").off
39
45
  # Create a group rule
40
46
  rule = Togls::Rules::Group.new(["user@email.com"])
41
- feature(:new_contact_form).on(rule)
47
+ feature(:new_contact_form, "use new contact form instead of old one").on(rule)
42
48
  end
43
49
  ```
44
50
 
45
- ### Evaluate
51
+ ### Evaluating Feature Toggles
52
+
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.
46
56
 
47
57
  ```ruby
58
+ if Togls.feature(:pop_up_login_form).on?
59
+ # Use pop up login form
60
+ else
61
+ # Use normal non-pop up login form
62
+ end
63
+
64
+ if Togls.feature(:send_follup_email).on?
65
+ # send the follow up email
66
+ end
67
+
48
68
  if Togls.feature(:new_contact_form).on?("user@email.com")
49
- # Do my awesome feature
69
+ # Use new contact form
70
+ else
71
+ # Use old contact form
72
+ end
73
+ ```
74
+
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
87
+
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.
91
+
92
+ ##### Load rake task in another gem
93
+
94
+ To use it in another gem you can use the following in the second gem's
95
+ `Rakefile`.
96
+
97
+ ```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
50
114
  end
115
+
116
+ spec = Gem::Specification.find_by_name 'togls'
117
+ load "#{spec.gem_dir}/lib/tasks/togls.rake"
118
+ ```
119
+
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.
126
+
127
+ #### Verify rake task loaded
128
+
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.
131
+
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
137
+
138
+ Once you have verified the task is loaded and available you can run it as
139
+ follows.
140
+
141
+ ```shell
142
+ rake togls:features
51
143
  ```
52
144
 
145
+ The following is an example of what the output might look like if you defined
146
+ a few test features.
147
+
148
+ ```text
149
+ on - :test1 - test 1 feature
150
+ off - :test2 - test 2 feature
151
+ off - :test3 - test 3 feature
152
+ ```
153
+
154
+ ## Advanced Usage
155
+
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.
158
+
53
159
  ### Custom Rules
54
160
 
55
- A simple rule can be defined by created a rule object and passing a block
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.
166
+
167
+ ### Simple Rules
168
+
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`.
56
172
 
57
173
  ```ruby
58
174
  # Only allow users with email addresses at gmail.com
@@ -63,7 +179,40 @@ Togls.features do
63
179
  end
64
180
  ```
65
181
 
66
- To implement a more complex rule a new rule object can be defined under Togls::Rules that implements the run method and returns a boolean. When a feature is defined, the rule will be added to the feature object and the run method called with whatever target is passed.
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
197
+
198
+ def run(target)
199
+ ...
200
+ end
201
+ ```
202
+
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`.
205
+
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.
213
+
214
+ A prime example of a *Complex Rule* provided by `Togls` is the
215
+ `Togls::Rules::Group` rule. You can see it below.
67
216
 
68
217
  ```ruby
69
218
  module Togls
@@ -81,6 +230,25 @@ module Togls
81
230
  end
82
231
  ```
83
232
 
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.
251
+
84
252
  ## Development
85
253
 
86
254
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,8 @@
1
+ namespace :togls do
2
+ desc "Output all features including status (on, off, ? - unknown due to complex rule), key, description"
3
+ task :features do
4
+ Togls.features.each do |key, feature|
5
+ puts feature.to_s
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Togls
2
+ class NoFeaturesError < StandardError; end
3
+ end
data/lib/togls/feature.rb CHANGED
@@ -1,25 +1,38 @@
1
1
  module Togls
2
2
  class Feature
3
- attr_reader :key
3
+ attr_reader :key, :description
4
4
 
5
- def initialize(key)
5
+ def initialize(key, description)
6
6
  @key = key
7
+ @description = description
7
8
  off
8
9
  end
9
10
 
10
11
  def on(rule = nil)
11
12
  if rule.nil?
12
- rule = Rule.new { true }
13
+ rule = Togls::Rules::Boolean.new(true)
13
14
  end
14
15
  @rule = rule
16
+ self
15
17
  end
16
18
 
17
19
  def off
18
- @rule = Rule.new { false }
20
+ @rule = Togls::Rules::Boolean.new(false)
21
+ self
19
22
  end
20
23
 
21
24
  def on?(target = nil)
22
25
  @rule.run(target)
23
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}"
36
+ end
24
37
  end
25
38
  end
@@ -2,7 +2,7 @@ module Togls
2
2
  class FeatureRegistry
3
3
  def initialize
4
4
  @registry = {}
5
- @default_feature = Feature.new(:default).tap {|f| f.on(Rule.new { false }) }
5
+ @default_feature = Feature.new(:default, "the official default feature").tap {|f| f.on(Rule.new { false }) }
6
6
  end
7
7
 
8
8
  def self.create(&features)
@@ -11,8 +11,8 @@ module Togls
11
11
  registry
12
12
  end
13
13
 
14
- def feature(tag)
15
- @registry[tag.to_sym] = Feature.new(tag.to_sym)
14
+ def feature(tag, desc)
15
+ @registry[tag.to_sym] = Feature.new(tag.to_sym, desc)
16
16
  end
17
17
 
18
18
  def get(key)
@@ -21,5 +21,9 @@ module Togls
21
21
  @default_feature
22
22
  end
23
23
  end
24
+
25
+ def registry
26
+ @registry
27
+ end
24
28
  end
25
29
  end
@@ -0,0 +1,13 @@
1
+ module Togls
2
+ module Rules
3
+ class Boolean < Rule
4
+ def initialize(bool)
5
+ @bool = bool
6
+ end
7
+
8
+ def run(target = nil)
9
+ return @bool
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/togls/rules.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'togls/rules/boolean'
1
2
  require 'togls/rules/group'
2
3
 
3
4
  module Togls
data/lib/togls/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Togls
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/togls.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "togls/version"
2
+ require "togls/errors"
2
3
  require "togls/feature_registry"
3
4
  require "togls/feature"
4
5
  require "togls/rule"
@@ -7,7 +8,15 @@ require "logger"
7
8
 
8
9
  module Togls
9
10
  def self.features(&features)
10
- @feature_registry = FeatureRegistry.create(&features)
11
+ if !features.nil?
12
+ @feature_registry = FeatureRegistry.create(&features)
13
+ else
14
+ if @feature_registry.nil?
15
+ raise Togls::NoFeaturesError, "Need to define features before you can get them"
16
+ else
17
+ @feature_registry.registry
18
+ end
19
+ end
11
20
  end
12
21
 
13
22
  def self.feature(key)
data/togls.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_development_dependency "bundler", "~> 1.8"
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
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: togls
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Miller
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2015-05-03 00:00:00.000000000 Z
12
+ date: 2015-05-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.8'
20
+ version: '1.9'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.8'
27
+ version: '1.9'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rake
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -75,11 +75,14 @@ files:
75
75
  - Rakefile
76
76
  - bin/console
77
77
  - bin/setup
78
+ - lib/tasks/togls.rake
78
79
  - lib/togls.rb
80
+ - lib/togls/errors.rb
79
81
  - lib/togls/feature.rb
80
82
  - lib/togls/feature_registry.rb
81
83
  - lib/togls/rule.rb
82
84
  - lib/togls/rules.rb
85
+ - lib/togls/rules/boolean.rb
83
86
  - lib/togls/rules/group.rb
84
87
  - lib/togls/version.rb
85
88
  - togls.gemspec