active_record_rollout 0.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +261 -0
- data/Rakefile +1 -0
- data/active_record_rollout.gemspec +32 -0
- data/lib/active_record/rollout.rb +35 -0
- data/lib/active_record/rollout/acts_as_flaggable.rb +36 -0
- data/lib/active_record/rollout/feature.rb +260 -0
- data/lib/active_record/rollout/flag.rb +13 -0
- data/lib/active_record/rollout/flaggable.rb +66 -0
- data/lib/active_record/rollout/flaggable_flag.rb +9 -0
- data/lib/active_record/rollout/group_flag.rb +7 -0
- data/lib/active_record/rollout/opt_out_flag.rb +9 -0
- data/lib/active_record/rollout/percentage_flag.rb +11 -0
- data/lib/active_record/rollout/version.rb +5 -0
- data/lib/generators/active_record_rollout_generator.rb +20 -0
- data/lib/generators/templates/active_record_rollout.rb +5 -0
- data/lib/generators/templates/migration.rb +24 -0
- data/lib/tasks/rollout.rake +119 -0
- data/spec/integration/flag_rollout_spec.rb +27 -0
- data/spec/integration/group_rollout_spec.rb +20 -0
- data/spec/integration/percentage_rollout_spec.rb +13 -0
- data/spec/lib/active_record/rollout/acts_as_flaggable_spec.rb +31 -0
- data/spec/lib/active_record/rollout/feature_spec.rb +235 -0
- data/spec/lib/active_record/rollout/flag_spec.rb +8 -0
- data/spec/lib/active_record/rollout/flaggable_flag_spec.rb +8 -0
- data/spec/lib/active_record/rollout/flaggable_spec.rb +149 -0
- data/spec/lib/active_record/rollout/group_flag_spec.rb +7 -0
- data/spec/lib/active_record/rollout/opt_out_flag_spec.rb +8 -0
- data/spec/lib/active_record/rollout/percentage_flag_spec.rb +10 -0
- data/spec/lib/tasks/rollout_rake_spec.rb +162 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/schema.rb +13 -0
- data/spec/support/shared_contexts/rake.rb +20 -0
- metadata +222 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# Indicates that a specific feature has been rolled out to an individual
|
2
|
+
# Table for storing flaggable flag-ins, group flag-ins, or percentage-based
|
3
|
+
# flag-ins.
|
4
|
+
class ActiveRecord::Rollout::Flag < ActiveRecord::Base
|
5
|
+
self.table_name = :active_record_rollout_flags
|
6
|
+
|
7
|
+
belongs_to :feature
|
8
|
+
|
9
|
+
validates :feature_id, presence: true
|
10
|
+
validates :flaggable_type, presence: true
|
11
|
+
|
12
|
+
attr_accessible :flaggable_type
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveRecord::Rollout::Flaggable
|
2
|
+
module ClassMethods
|
3
|
+
# Finds a record by the field set by the :find_by param in
|
4
|
+
# `acts_as_flaggable`. If no :find_by param was provided, :id is used.
|
5
|
+
#
|
6
|
+
# @param [String,Integer] value The value to find the record by.
|
7
|
+
def flaggable_find!(value)
|
8
|
+
send("find_by_#{@active_record_rollout_flaggable_find_by}!", value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns whether or not the object has access to the given feature. If given
|
13
|
+
# a block, it will call the block if the user has access to the feature.
|
14
|
+
#
|
15
|
+
# If an exception is raised in the block, it will increment the
|
16
|
+
# `failure_count` of the feature and raise the exception.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # Exceptions will be tracked in the `failure_count` of :new_user_interface.
|
20
|
+
# user.has_feature?(:new_user_interface) do
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# # Exceptions will *not* be tracked in the `failure_count` of :new_user_interface.
|
26
|
+
# if user.has_feature?(:new_user_interface)
|
27
|
+
# # ...
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @param [Symbol] feature_name The name of the
|
31
|
+
# {ActiveRecord::Rollout::Feature Feature} being checked.
|
32
|
+
# @param [Proc] &block A block to be called if the user is flagged in to the
|
33
|
+
# feature.
|
34
|
+
def has_feature?(feature_name, &block)
|
35
|
+
if active_record_rollout_features.include? feature_name.to_s
|
36
|
+
match = true
|
37
|
+
else
|
38
|
+
feature = ActiveRecord::Rollout::Feature.find_by_name(feature_name)
|
39
|
+
return false unless feature
|
40
|
+
|
41
|
+
opt_out = opt_out_flags.find_by_feature_id(feature.id)
|
42
|
+
return false if opt_out
|
43
|
+
|
44
|
+
match = feature.match? self
|
45
|
+
|
46
|
+
if match
|
47
|
+
active_record_rollout_features << feature.name.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if match && block_given?
|
52
|
+
begin
|
53
|
+
yield
|
54
|
+
rescue => e
|
55
|
+
feature.increment! :failure_count
|
56
|
+
raise e
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
match
|
61
|
+
end
|
62
|
+
|
63
|
+
def active_record_rollout_features
|
64
|
+
@active_record_rollout_features ||= []
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# An individual record of a certain type may be flagged into a feature with
|
2
|
+
# this class.
|
3
|
+
class ActiveRecord::Rollout::FlaggableFlag < ActiveRecord::Rollout::Flag
|
4
|
+
belongs_to :flaggable, polymorphic: true
|
5
|
+
|
6
|
+
validates :flaggable_id, presence: true
|
7
|
+
|
8
|
+
attr_accessible :flaggable
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Ensures that a feature will never be available to the associated record,
|
2
|
+
# even in the case of, for example, a 100% flag.
|
3
|
+
class ActiveRecord::Rollout::OptOutFlag < ActiveRecord::Rollout::Flag
|
4
|
+
belongs_to :flaggable, polymorphic: true
|
5
|
+
|
6
|
+
validates :flaggable_id, presence: true
|
7
|
+
|
8
|
+
attr_accessible :flaggable
|
9
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# A percentage of flaggable records of a given class may be flagged into a feature
|
2
|
+
# with this class.
|
3
|
+
class ActiveRecord::Rollout::PercentageFlag < ActiveRecord::Rollout::Flag
|
4
|
+
validates :percentage,
|
5
|
+
presence: true,
|
6
|
+
numericality: { greater_than: 0, less_than_or_equal_to: 100 }
|
7
|
+
|
8
|
+
validates :feature_id, uniqueness: { scope: :flaggable_type }
|
9
|
+
|
10
|
+
attr_accessible :percentage
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "active_record/rollout"
|
2
|
+
require "rails/generators"
|
3
|
+
require "rails/generators/active_record"
|
4
|
+
|
5
|
+
class ActiveRecordRolloutGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
extend ActiveRecord::Generators::Migration
|
8
|
+
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
10
|
+
|
11
|
+
desc "Creates migration for ActiveRecord::Rollout"
|
12
|
+
def create_migration_file
|
13
|
+
migration_template "migration.rb", "db/migrate/setup_active_record_rollout.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Sets up an initializer for ActiveRecord::Rollout"
|
17
|
+
def create_initializer
|
18
|
+
copy_file "active_record_rollout.rb", "config/initializers/active_record_rollout.rb"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class SetupActiveRecordRollout < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :active_record_rollout_features do |t|
|
4
|
+
t.string :name
|
5
|
+
t.integer :failure_count, default: 0
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
add_index :active_record_rollout_features, :name, unique: true
|
10
|
+
|
11
|
+
create_table :active_record_rollout_flags do |t|
|
12
|
+
t.string :type
|
13
|
+
t.integer :feature_id
|
14
|
+
t.integer :flaggable_id
|
15
|
+
t.string :flaggable_type
|
16
|
+
t.string :group_name
|
17
|
+
t.integer :percentage
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
|
21
|
+
add_index :active_record_rollout_flags, :type
|
22
|
+
add_index :active_record_rollout_flags, :feature_id
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
namespace :rollout do
|
2
|
+
desc "Create a feature"
|
3
|
+
task :create, [:feature] => :environment do |task, args|
|
4
|
+
ActiveRecord::Rollout::Feature.find_or_create_by_name! args[:feature]
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Destroy a feature"
|
8
|
+
task :destroy, [:feature] => :environment do |task, args|
|
9
|
+
feature = ActiveRecord::Rollout::Feature.find_by_name! args[:feature]
|
10
|
+
feature.destroy
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Activate a feature for a record"
|
14
|
+
task :activate, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
|
15
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
16
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name.constantize
|
17
|
+
record_locator = args[:flaggable_type]
|
18
|
+
else
|
19
|
+
klass = args[:flaggable_type].constantize
|
20
|
+
record_locator = args[:flaggable_id]
|
21
|
+
end
|
22
|
+
|
23
|
+
record = klass.flaggable_find! record_locator
|
24
|
+
|
25
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature record, args[:feature]
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Deactivate a feature for a record"
|
29
|
+
task :deactivate, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
|
30
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
31
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name.constantize
|
32
|
+
record_locator = args[:flaggable_type]
|
33
|
+
else
|
34
|
+
klass = args[:flaggable_type].constantize
|
35
|
+
record_locator = args[:flaggable_id]
|
36
|
+
end
|
37
|
+
|
38
|
+
record = klass.flaggable_find! record_locator
|
39
|
+
ActiveRecord::Rollout::Feature.remove_record_from_feature record, args[:feature]
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Opt a record out of a feature"
|
43
|
+
task :opt_out, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
|
44
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
45
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name.constantize
|
46
|
+
record_locator = args[:flaggable_type]
|
47
|
+
else
|
48
|
+
klass = args[:flaggable_type].constantize
|
49
|
+
record_locator = args[:flaggable_id]
|
50
|
+
end
|
51
|
+
|
52
|
+
record = klass.flaggable_find! record_locator
|
53
|
+
ActiveRecord::Rollout::Feature.opt_record_out_of_feature record, args[:feature]
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Remove an opt out of a record from a feature"
|
57
|
+
task :un_opt_out, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
|
58
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
59
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name.constantize
|
60
|
+
record_locator = args[:flaggable_type]
|
61
|
+
else
|
62
|
+
klass = args[:flaggable_type].constantize
|
63
|
+
record_locator = args[:flaggable_id]
|
64
|
+
end
|
65
|
+
|
66
|
+
record = klass.flaggable_find! record_locator
|
67
|
+
ActiveRecord::Rollout::Feature.un_opt_record_out_of_feature record, args[:feature]
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Activate a feature for a group"
|
71
|
+
task :activate_group, [:feature, :flaggable_type, :group_name] => :environment do |task, args|
|
72
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
73
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
74
|
+
group_name = args[:flaggable_type]
|
75
|
+
else
|
76
|
+
klass = args[:flaggable_type]
|
77
|
+
group_name = args[:group_name]
|
78
|
+
end
|
79
|
+
|
80
|
+
ActiveRecord::Rollout::Feature.add_group_to_feature klass, group_name, args[:feature]
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Deactivate a feature for a group"
|
84
|
+
task :deactivate_group, [:feature, :flaggable_type, :group_name] => :environment do |task, args|
|
85
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
86
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
87
|
+
group_name = args[:flaggable_type]
|
88
|
+
else
|
89
|
+
klass = args[:flaggable_type]
|
90
|
+
group_name = args[:group_name]
|
91
|
+
end
|
92
|
+
|
93
|
+
ActiveRecord::Rollout::Feature.remove_group_from_feature klass, group_name, args[:feature]
|
94
|
+
end
|
95
|
+
|
96
|
+
desc "Activate a feature for a percentage"
|
97
|
+
task :activate_percentage, [:feature, :flaggable_type, :percentage] => :environment do |task, args|
|
98
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
99
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
100
|
+
percentage = args[:flaggable_type]
|
101
|
+
else
|
102
|
+
klass = args[:flaggable_type]
|
103
|
+
percentage = args[:percentage]
|
104
|
+
end
|
105
|
+
|
106
|
+
ActiveRecord::Rollout::Feature.add_percentage_to_feature klass, percentage.to_i, args[:feature]
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "Deactivate a feature for a percentage"
|
110
|
+
task :deactivate_percentage, [:feature, :flaggable_type] => :environment do |task, args|
|
111
|
+
if args.to_a.length < 3 && ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
112
|
+
klass = ActiveRecord::Rollout::Feature.default_flaggable_class_name
|
113
|
+
else
|
114
|
+
klass = args[:flaggable_type]
|
115
|
+
end
|
116
|
+
|
117
|
+
ActiveRecord::Rollout::Feature.remove_percentage_from_feature klass, args[:feature]
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "flag rollouts" do
|
4
|
+
let(:user) { User.create }
|
5
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create(name: "foo") }
|
6
|
+
|
7
|
+
describe "creating a flag rollout" do
|
8
|
+
before do
|
9
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature user, feature.name
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets the feature on the user" do
|
13
|
+
feature.match_id?(user).should be_true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "removing a flag rollout" do
|
18
|
+
before do
|
19
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature user, feature.name
|
20
|
+
ActiveRecord::Rollout::Feature.remove_record_from_feature user, feature.name
|
21
|
+
end
|
22
|
+
|
23
|
+
it "removes the feature from the user" do
|
24
|
+
feature.match_id?(user).should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "group rollouts" do
|
4
|
+
let(:user) { User.create(name: "foo") }
|
5
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create(name: "foo") }
|
6
|
+
let!(:flag) { feature.group_flags.create(flaggable_type: "User", group_name: "foo_users") }
|
7
|
+
|
8
|
+
describe "creating a group rollout" do
|
9
|
+
before do
|
10
|
+
ActiveRecord::Rollout::Feature.define_user_group "foo_users" do |user|
|
11
|
+
user.name == "foo"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets the feature on the user" do
|
16
|
+
feature.match_groups?(user).should be_true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "percentage rollouts" do
|
4
|
+
let(:users) { 10.times.collect { User.create } }
|
5
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create(name: "foo") }
|
6
|
+
let!(:flag) { feature.percentage_flags.create(flaggable_type: "User", percentage: 20) }
|
7
|
+
|
8
|
+
describe "creating a percentage rollout" do
|
9
|
+
it "makes the feature available to the given percentage of instances" do
|
10
|
+
users.select { |user| feature.match_percentage?(user) }.length.should eq users.length / 5
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::Rollout::ActsAsFlaggable do
|
4
|
+
subject { User.new }
|
5
|
+
|
6
|
+
it { should have_many :flaggable_flags }
|
7
|
+
it { should have_many :opt_out_flags }
|
8
|
+
it { should have_many(:features).through(:flaggable_flags) }
|
9
|
+
|
10
|
+
it "includes ActiveRecord::Rollout::Flaggable" do
|
11
|
+
subject.class.ancestors.should include ActiveRecord::Rollout::Flaggable
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#acts_as_flaggable" do
|
15
|
+
context "when given a :find_by parameter" do
|
16
|
+
class Foo < ActiveRecord::Base
|
17
|
+
acts_as_flaggable find_by: :email
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets the appropriate class variable on the class" do
|
21
|
+
Foo.instance_variable_get("@active_record_rollout_flaggable_find_by").should eq :email
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when not given a :find_by parameter" do
|
26
|
+
it "uses the default :id value for flaggable_find_by" do
|
27
|
+
User.instance_variable_get("@active_record_rollout_flaggable_find_by").should eq :id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::Rollout::Feature do
|
4
|
+
it { should have_many(:flaggable_flags) }
|
5
|
+
it { should have_many(:group_flags) }
|
6
|
+
it { should have_many(:percentage_flags) }
|
7
|
+
it { should have_many(:opt_out_flags) }
|
8
|
+
it { should have_many(:flags).dependent(:destroy) }
|
9
|
+
it { should validate_presence_of :name }
|
10
|
+
it { should validate_uniqueness_of :name }
|
11
|
+
it { should allow_mass_assignment_of :name }
|
12
|
+
|
13
|
+
describe ".define_{klass}_group" do
|
14
|
+
let(:block) { Proc.new {} }
|
15
|
+
it "defines a group for the given class" do
|
16
|
+
ActiveRecord::Rollout::Feature.should_receive(:define_group_for_class).with("User", :id_is_1)
|
17
|
+
ActiveRecord::Rollout::Feature.define_user_group :id_is_1, block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".add_record_to_feature" do
|
22
|
+
let(:user) { User.create }
|
23
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
24
|
+
|
25
|
+
before do
|
26
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature user, :foo
|
27
|
+
end
|
28
|
+
|
29
|
+
it "creats a flag for the given instance and feature" do
|
30
|
+
user.features.should include feature
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".remove_record_from_feature" do
|
35
|
+
let(:user) { User.create }
|
36
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
37
|
+
|
38
|
+
before do
|
39
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature user, :foo
|
40
|
+
ActiveRecord::Rollout::Feature.remove_record_from_feature user, :foo
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creats a flag for the given instance and feature" do
|
44
|
+
user.features.should_not include feature
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".opt_record_out_of_feature" do
|
49
|
+
let(:user) { User.create }
|
50
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
51
|
+
|
52
|
+
before do
|
53
|
+
ActiveRecord::Rollout::Feature.add_percentage_to_feature "User", 100, "foo"
|
54
|
+
ActiveRecord::Rollout::Feature.opt_record_out_of_feature user, "foo"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "opts the record out of the feature" do
|
58
|
+
user.has_feature?("foo").should be_false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ".un_opt_record_out_of_feature" do
|
63
|
+
let(:user) { User.create }
|
64
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
65
|
+
|
66
|
+
before do
|
67
|
+
ActiveRecord::Rollout::Feature.add_percentage_to_feature "User", 100, "foo"
|
68
|
+
ActiveRecord::Rollout::Feature.opt_record_out_of_feature user, "foo"
|
69
|
+
ActiveRecord::Rollout::Feature.un_opt_record_out_of_feature user, "foo"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "opts the record out of the feature" do
|
73
|
+
user.has_feature?("foo").should be_true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ".add_group_to_feature" do
|
78
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
79
|
+
|
80
|
+
before do
|
81
|
+
ActiveRecord::Rollout::Feature.define_user_group :bar do
|
82
|
+
end
|
83
|
+
ActiveRecord::Rollout::Feature.add_group_to_feature "User", :bar, "foo"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "creates a flag for the given group and feature" do
|
87
|
+
feature.group_flags.where(flaggable_type: "User", group_name: "bar").first.should_not be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe ".remove_group_from_feature" do
|
92
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
93
|
+
|
94
|
+
before do
|
95
|
+
ActiveRecord::Rollout::Feature.define_user_group :bar do
|
96
|
+
end
|
97
|
+
ActiveRecord::Rollout::Feature.add_group_to_feature "User", :bar, "foo"
|
98
|
+
ActiveRecord::Rollout::Feature.remove_group_from_feature "User", :bar, "foo"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "destroys flags for the given group and feature" do
|
102
|
+
feature.group_flags.where(flaggable_type: "User", group_name: "bar").first.should be_nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".add_percentage_to_feature" do
|
107
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
108
|
+
|
109
|
+
before do
|
110
|
+
ActiveRecord::Rollout::Feature.add_percentage_to_feature "User", 50, "foo"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "creates a flag for the given percentage and feature" do
|
114
|
+
feature.percentage_flags.where(flaggable_type: "User", percentage: 50).first.should_not be_nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe ".remove_percentage_from_feature" do
|
119
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
120
|
+
|
121
|
+
before do
|
122
|
+
ActiveRecord::Rollout::Feature.add_percentage_to_feature "User", 50, "foo"
|
123
|
+
ActiveRecord::Rollout::Feature.remove_percentage_from_feature "User", "foo"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "creates a flag for the given percentage and feature" do
|
127
|
+
feature.percentage_flags.where(flaggable_type: "User").first.should be_nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe ".define_group_for_class" do
|
132
|
+
let(:block) { Proc.new {} }
|
133
|
+
before do
|
134
|
+
ActiveRecord::Rollout::Feature.send :define_group_for_class, "User", "user_id_1", &block
|
135
|
+
end
|
136
|
+
|
137
|
+
it "defines a group for the given class" do
|
138
|
+
ActiveRecord::Rollout::Feature.defined_groups["User"].should eq({ "user_id_1" => block })
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#match?" do
|
143
|
+
let(:user) { User.create }
|
144
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create(name: "foo") }
|
145
|
+
|
146
|
+
it "checks if the user is flagged individually" do
|
147
|
+
feature.should_receive(:match_id?).with(user)
|
148
|
+
feature.match?(user)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "checks if the user is flagged as part of a percentage" do
|
152
|
+
feature.should_receive(:match_percentage?).with(user)
|
153
|
+
feature.match?(user)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "checks if the user is flagged as part of a group" do
|
157
|
+
feature.should_receive(:match_groups?).with(user)
|
158
|
+
feature.match?(user)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "#match_id?" do
|
163
|
+
let(:user) { User.create }
|
164
|
+
let(:user2) { User.create }
|
165
|
+
let!(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
166
|
+
|
167
|
+
before do
|
168
|
+
ActiveRecord::Rollout::Feature.add_record_to_feature user, :foo
|
169
|
+
end
|
170
|
+
|
171
|
+
context "when the feature exists for the instance" do
|
172
|
+
it "returns true" do
|
173
|
+
feature.match_id?(user).should be_true
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "when the feature does not exist for the instance" do
|
178
|
+
it "returns false" do
|
179
|
+
feature.match_id?(user2).should be_false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#match_percentage?" do
|
185
|
+
let(:user) { User.create }
|
186
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create!(name: "foo") }
|
187
|
+
let!(:flag) { feature.percentage_flags.create(flaggable_type: "User", percentage: 50) }
|
188
|
+
|
189
|
+
context "when the user's ID matches `id % 10 < percentage / 10" do
|
190
|
+
it "returns true" do
|
191
|
+
user.stub(:id) { 1 }
|
192
|
+
feature.match_percentage?(user).should be_true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "when the user's ID does not match `id % 10 < percentage / 10" do
|
197
|
+
it "returns false" do
|
198
|
+
user.stub(:id) { 5 }
|
199
|
+
feature.match_percentage?(user).should be_false
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "#match_groups?" do
|
205
|
+
let!(:user) { User.create(name: "foo") }
|
206
|
+
let!(:user2) { User.create(name: "bar") }
|
207
|
+
let!(:organization) { Organization.create(name: "foo") }
|
208
|
+
let(:feature) { ActiveRecord::Rollout::Feature.create!(name: "baz") }
|
209
|
+
let!(:flag) { feature.group_flags.create(flaggable_type: "User", group_name: "foo_users") }
|
210
|
+
|
211
|
+
before do
|
212
|
+
ActiveRecord::Rollout::Feature.define_user_group "foo_users" do |user|
|
213
|
+
user.name == "foo"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context "when the instance matches the block" do
|
218
|
+
it "returns true" do
|
219
|
+
feature.match_groups?(user).should be_true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context "when the instance does not match the block" do
|
224
|
+
it "returns false" do
|
225
|
+
feature.match_groups?(user2).should be_false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "when the instance is not of the type of the block" do
|
230
|
+
it "returns false" do
|
231
|
+
feature.match_groups?(organization).should be_false
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|