step_by_step 0.0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 914546dc63e40d0896c7b86b82b42671ae777299
4
+ data.tar.gz: 95cf8bf3d06b7c14d629669f214a23e4fae5c76a
5
+ SHA512:
6
+ metadata.gz: 1d06e4a64e8249df4fa010ab93952490c8fb1aecabaa01c0d17ff1ac7cc64a3bc926a2bb18f091805f18994a7b5b6fc2927b7883acc5eb18624db7208fbd1fdb
7
+ data.tar.gz: 5ce8f9b3a8b4a796242ea15e6b0d8722de1c1c80a0e0250b5c7fe20468029509631cacfd89547843b7a25a4a10fa13e28f0f3f9ba61ddfc259353acbbc8887b1
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in step_by_step.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 CrowdCurity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dennis Charles Hackethal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # StepByStep
2
+
3
+ This gem is largely inspired by the [Railscast on rollouts and degrading](http://railscasts.com/episodes/315-rollout-and-degrade), as well as the [Rollout gem](https://github.com/FetLife/rollout) that is covered in aforementioned Railscast. While very comprehensive, it uses a Redis backend, that might not be desirable to everyone. Though there are some alternatives out there, most of them are either outdated or not as comprehensive as the rollout gem itself.
4
+
5
+ StepByStep uses ActiveRecord as backend instead and tries to cover as many use cases and features as the original rollout gem. It is easy to use since no additional backend needs to be set up, and adds some helpful methods that shall assist you in rolling out and degrading features reliably.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'step_by_step'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install step_by_step
20
+
21
+ Since StepByStep uses ActiveRecord, you need to create the following migration:
22
+
23
+ $ rails g migration create_rollouts name group user_id:integer percentage:integer failure_count:integer
24
+
25
+ Then migrate your database.
26
+
27
+ ## Usage
28
+
29
+ Let's say you have this new commenting feature that you would like to deploy to production, but you only want a subset of your users to be able to see it. Also, you may want to degrade a feature later, or disable it again altogether.
30
+
31
+ ### Rolling Out Features
32
+
33
+ #### Rolling Out To A Group Of Users
34
+
35
+ You can define groups with any custom logic like this:
36
+
37
+ ```ruby
38
+ StepByStep::Rollout.define_group :admins do |user|
39
+ user.admin?
40
+ end
41
+ ```
42
+
43
+ Consider putting this in an initializer, such as `config/initializers/step_by_step.rb`.
44
+
45
+ Roll out your feature to your group:
46
+
47
+ ```ruby
48
+ StepByStep::Rollout.activate_group(:comments, :admins)
49
+ ```
50
+
51
+ #### Rolling Out To A Fraction Of Users
52
+
53
+ ```ruby
54
+ StepByStep::Rollout.activate_percentage(:comments, 20)
55
+ end
56
+ ```
57
+
58
+ Now 20% of your users can see the comments feature. As in the original gem, this is based on the following algorithm:
59
+
60
+ ```
61
+ CRC32(user.id) % 100 < percentage # pseudocode
62
+ ```
63
+
64
+ So, for 20%, a feature will be rolled out to users with ids 0, 1, 10, 11, 20, 21, etc. These users also remain as the percentage increases.
65
+
66
+ #### Rolling Out To A Specific User
67
+
68
+ ```ruby
69
+ StepByStep::Rollout.activate_user(:comments, User.first)
70
+ ```
71
+
72
+ Now your first user can see the comments feature.
73
+
74
+ #### Rolling Out To Everyone
75
+
76
+ ```ruby
77
+ StepByStep::Rollout.activate(:comments)
78
+ ```
79
+
80
+ Now everyone can see the comments feature. This is theoretically the same as activating a percentage with value 100 or defining a group with a block that always returns true.
81
+
82
+ Activating a feature for everyone is common after you have determined that your rollout phase was successful.
83
+
84
+ ### Deactivating Features
85
+
86
+ You can easily deactivate new features depending on your needs.
87
+
88
+ #### Deactivating A Feature For Everyone
89
+
90
+ ```ruby
91
+ StepByStep::Rollout.deactivate(:comments)
92
+ ```
93
+
94
+ Nobody can see your new comments feature anymore.
95
+
96
+ #### Deactivating A Group Feature
97
+
98
+ Your admins shouldn't be able to see the comments anymore? Easy:
99
+
100
+ ```ruby
101
+ StepByStep::Rollout.deactivate_group(:comments, :admins)
102
+ ```
103
+
104
+ #### Deactivating A Single User Feature
105
+
106
+ And there this one user who could see the comments. Let's hide them from him again:
107
+
108
+ ```ruby
109
+ StepByStep::Rollout.deactivate_user(:comments, User.first)
110
+ ```
111
+
112
+ #### Deactivating A Feature For A Fraction Of Users
113
+
114
+ Remember those 20% who could see your new feature? Let's get rid of them, too:
115
+
116
+ ```ruby
117
+ StepByStep::Rollout.deactivate_percentage(:comments)
118
+ ```
119
+
120
+ ### Displaying Features
121
+
122
+ StepByStep comes with a few helper methods, one of which is called `rollout?`. It's available in your controllers as well as in your views. You just pass it the feature name to check if it has been rolled out to your `current_user`.
123
+
124
+ #### View Example
125
+
126
+ It's as simple as:
127
+
128
+ ```erb
129
+ <% if rollout?(:comments) %>
130
+ <%= # your awesome feature here %>
131
+ <% end %>
132
+ ```
133
+
134
+ #### Controller Example
135
+
136
+ Sometimes, you may want to hide a view completely. You can either do this directly in a controller action:
137
+
138
+ ```ruby
139
+ def show
140
+ unless rollout?(:comments)
141
+ redirect_to root_path, notice: 'Access denied'
142
+ end
143
+ end
144
+ ```
145
+
146
+ or preferably using before actions:
147
+
148
+ ```ruby
149
+ before_action :rollout
150
+
151
+ private
152
+ def rollout
153
+ unless rollout?(:comments)
154
+ redirect_to root_path, notice: 'Access denied'
155
+ end
156
+ end
157
+ ```
158
+
159
+ ### Degrading A Feature
160
+
161
+ Every rollout comes with a `failure_count`. A helper method is added to your application controller that allows you to track exceptions and increments the failure count for your feature. A failure count of 1 or higher disables your feature.
162
+
163
+ For instance, you could degrade a feature doing the following in your feature controller:
164
+
165
+ ```ruby
166
+ around_action :degrade
167
+
168
+ private
169
+ def degrade
170
+ degrade_feature(:comments) { yield }
171
+ end
172
+ ```
173
+
174
+ ## Foolish Assumptions
175
+
176
+ - You have a `current_user` method in your application controller that returns the authenticated user or nil if not authenticated (standard behavior, used e.g. by Devise)
177
+ - You are using Rails
178
+ - Your user model's primary key is `id` (Rails default)
179
+
180
+ ## Dependencies
181
+
182
+ StepByStep requires Rails 3.2 or higher.
183
+
184
+ ## Contributing
185
+
186
+ 1. Fork it ( https://github.com/[my-github-username]/step_by_step/fork )
187
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
188
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
189
+ 4. Push to the branch (`git push origin my-new-feature`)
190
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,13 @@
1
+ require "active_record"
2
+ require "rails"
3
+ require "step_by_step/version"
4
+ require "step_by_step/rollout"
5
+ require "step_by_step/controller"
6
+
7
+ module StepByStep
8
+ class Engine < ::Rails::Engine
9
+ initializer 'step_by_step.controllers' do |app|
10
+ ActionController::Base.send :include, StepByStep::Controller
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module StepByStep
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :rollout?
7
+ hide_action :rollout?
8
+
9
+ hide_action :degrade_feature
10
+ end
11
+
12
+ def rollout?(name)
13
+ Rollout.where(name: name).any? do |rollout|
14
+ rollout.match?(current_user)
15
+ end
16
+ end
17
+
18
+ def degrade_feature(name)
19
+ yield
20
+ rescue StandardError => e
21
+ Rollout.where(name: name).each do |rollout|
22
+ rollout.increment!(:failure_count)
23
+ end
24
+ raise e
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ module StepByStep
2
+ class Rollout < ActiveRecord::Base
3
+ def match?(user)
4
+ user && enabled? && (match_group?(user) || match_user?(user) || match_percentage?(user))
5
+ end
6
+
7
+ # Activates a feature for everyone
8
+ def self.activate(name)
9
+ create! name: name, group: :all
10
+ end
11
+
12
+ # Activates a feature for a specific group
13
+ def self.activate_group(name, group)
14
+ create! name: name, group: group
15
+ end
16
+
17
+ # Activates a feature for a fraction of users
18
+ def self.activate_percentage(name, percentage)
19
+ create! name: name, percentage: percentage
20
+ end
21
+
22
+ # Activates a feature for a specific user
23
+ def self.activate_user(name, user)
24
+ create! name: name, user_id: user.id
25
+ end
26
+
27
+ # Deactivates a feature for everyone
28
+ def self.deactivate(name)
29
+ where(name: name).destroy_all
30
+ end
31
+
32
+ # Deactivates a feature for a specific group
33
+ def self.deactivate_group(name, group)
34
+ where(name: name, group: group).destroy_all
35
+ end
36
+
37
+ # Deactivates a feature for a specific user
38
+ def self.deactivate_user(name, user)
39
+ where(name: name, user_id: user.id).destroy_all
40
+ end
41
+
42
+ # Deactivates a feature for a fraction of users
43
+ def self.deactivate_percentage(name)
44
+ where('name = ? AND percentage IS NOT NULL', name).destroy_all
45
+ end
46
+
47
+ # Define groups
48
+ def self.define_group(group, &block)
49
+ @@groups ||= []
50
+ @@groups << [group.to_sym, ->(user){ yield(user) }]
51
+ end
52
+
53
+ define_group(:all) do
54
+ true
55
+ end
56
+
57
+ private
58
+ def enabled?
59
+ failure_count.to_i < 1
60
+ end
61
+
62
+ def match_group?(user)
63
+ if group
64
+ # Find the group whose block should be evaluated based on its name
65
+ g = @@groups.select { |i| i.first.to_sym == group.to_sym }
66
+
67
+ # There could theoretically be multiple groups with the same name;
68
+ # only take the last one
69
+ g = g.last
70
+
71
+ # g is now an array that looks like this:
72
+ # [:group_name, code_block]
73
+ # Call the code block here with the user
74
+ g.last.call(user)
75
+ end
76
+ end
77
+
78
+ def match_user?(user)
79
+ user_id ? user_id == user.id : false
80
+ end
81
+
82
+ def match_percentage?(user)
83
+ percentage ? Zlib::crc32(user.id.to_s) % 100 < percentage : false
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module StepByStep
2
+ VERSION = "0.0.1.5"
3
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ module StepByStep
4
+ describe Rollout do
5
+ let!(:user) { Struct.new(:id, :email) }
6
+
7
+ # User who matches
8
+ let!(:user_1) { user.new(1, 'test@test.com') }
9
+
10
+ # User who doesn't match
11
+ let!(:user_2) { user.new(2, 'nope@test.com') }
12
+
13
+ # User who is not logged in (current user will return nil if nobody is logged in)
14
+ let!(:user_3) { nil }
15
+
16
+ before :all do
17
+ Rollout.define_group(:admins) do |user|
18
+ emails = [
19
+ 'test@test.com'
20
+ ]
21
+ emails.include?(user.email)
22
+ end
23
+ end
24
+
25
+ it 'rolls out to all' do
26
+ rollout = Rollout.activate(:feature)
27
+ expect(rollout.match?(user_1)).to be_truthy
28
+ expect(rollout.match?(user_2)).to be_truthy
29
+ end
30
+
31
+ it 'rolls out to a group' do
32
+ rollout = Rollout.activate_group(:feature, :admins)
33
+ expect(rollout.match?(user_1)).to be_truthy
34
+ expect(rollout.match?(user_2)).to be_falsey
35
+ end
36
+
37
+ it 'rolls out to a user' do
38
+ rollout = Rollout.activate_user(:feature, user_1)
39
+ expect(rollout.match?(user_1)).to be_truthy
40
+ expect(rollout.match?(user_2)).to be_falsey
41
+ end
42
+
43
+ it 'rolls out to a percentage' do
44
+ rollout = Rollout.activate_percentage(:feature, 50)
45
+
46
+ expect(rollout.match?(user_1)).to be_falsey
47
+ expect(rollout.match?(user_2)).to be_truthy
48
+ end
49
+
50
+ it 'handles users who are not logged in' do
51
+ rollout = Rollout.activate_group(:feature, :admins)
52
+ expect(rollout.match?(user_3)).to be_falsey
53
+ end
54
+
55
+ it 'deactivates all' do
56
+ Rollout.activate_group(:feature, :all)
57
+ Rollout.deactivate(:feature)
58
+ expect(Rollout.where(name: :feature).any?).to be_falsey
59
+ end
60
+
61
+ it 'deactivates a group' do
62
+ Rollout.activate_group(:feature, :admins)
63
+ Rollout.deactivate_group(:feature, :admins)
64
+ expect(Rollout.where(name: :feature, group: :admins).any?).to be_falsey
65
+ end
66
+
67
+ it 'deactivates a user' do
68
+ Rollout.activate_user(:feature, user_1)
69
+ Rollout.deactivate_user(:feature, user_1)
70
+ expect(Rollout.where(name: :feature, user_id: user_1.id).any?).to be_falsey
71
+ end
72
+
73
+ it 'deactivates a percentage' do
74
+ Rollout.activate_percentage(:feature, 50)
75
+ Rollout.deactivate_percentage(:feature)
76
+ expect(Rollout.where('name = ? AND percentage IS NOT NULL', :feature).any?).to be_falsey
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,10 @@
1
+ require "step_by_step"
2
+
3
+ # Establish a connection to db
4
+ ActiveRecord::Base.establish_connection(
5
+ adapter: "sqlite3",
6
+ database: File.dirname(__FILE__) + "/step_by_step.sqlite3"
7
+ )
8
+
9
+ # Load schema every time the specs are run
10
+ load File.dirname(__FILE__) + '/support/schema.rb'
Binary file
@@ -0,0 +1,13 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :rollouts, force: true do |t|
5
+ t.string :name
6
+ t.string :group
7
+ t.integer :user_id
8
+ t.integer :percentage
9
+ t.integer :failure_count
10
+
11
+ t.timestamps
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'step_by_step/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "step_by_step"
8
+ spec.version = StepByStep::VERSION
9
+ spec.authors = ["Dennis Charles Hackethal"]
10
+ spec.email = ["dennis.hackethal@gmail.com"]
11
+ spec.summary = %q{Active Record alternative to https://github.com/FetLife/rollout.}
12
+ spec.description = %q{Alternative to https://github.com/FetLife/rollout, with an Active Record backend and additional helpers, partially based on Ryan Bates's custom solution in http://railscasts.com/episodes/315-rollout-and-degrade.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec-rails"
24
+ spec.add_development_dependency "sqlite3"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_dependency 'rails', '>= 3.2'
27
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: step_by_step
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Dennis Charles Hackethal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.2'
97
+ description: Alternative to https://github.com/FetLife/rollout, with an Active Record
98
+ backend and additional helpers, partially based on Ryan Bates's custom solution
99
+ in http://railscasts.com/episodes/315-rollout-and-degrade.
100
+ email:
101
+ - dennis.hackethal@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - Gemfile
108
+ - LICENSE
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - lib/step_by_step.rb
113
+ - lib/step_by_step/controller.rb
114
+ - lib/step_by_step/rollout.rb
115
+ - lib/step_by_step/version.rb
116
+ - spec/rollout_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/step_by_step.sqlite3
119
+ - spec/support/schema.rb
120
+ - step_by_step.gemspec
121
+ homepage: ''
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Active Record alternative to https://github.com/FetLife/rollout.
145
+ test_files:
146
+ - spec/rollout_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/step_by_step.sqlite3
149
+ - spec/support/schema.rb