active_record_rollout 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|