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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +261 -0
  7. data/Rakefile +1 -0
  8. data/active_record_rollout.gemspec +32 -0
  9. data/lib/active_record/rollout.rb +35 -0
  10. data/lib/active_record/rollout/acts_as_flaggable.rb +36 -0
  11. data/lib/active_record/rollout/feature.rb +260 -0
  12. data/lib/active_record/rollout/flag.rb +13 -0
  13. data/lib/active_record/rollout/flaggable.rb +66 -0
  14. data/lib/active_record/rollout/flaggable_flag.rb +9 -0
  15. data/lib/active_record/rollout/group_flag.rb +7 -0
  16. data/lib/active_record/rollout/opt_out_flag.rb +9 -0
  17. data/lib/active_record/rollout/percentage_flag.rb +11 -0
  18. data/lib/active_record/rollout/version.rb +5 -0
  19. data/lib/generators/active_record_rollout_generator.rb +20 -0
  20. data/lib/generators/templates/active_record_rollout.rb +5 -0
  21. data/lib/generators/templates/migration.rb +24 -0
  22. data/lib/tasks/rollout.rake +119 -0
  23. data/spec/integration/flag_rollout_spec.rb +27 -0
  24. data/spec/integration/group_rollout_spec.rb +20 -0
  25. data/spec/integration/percentage_rollout_spec.rb +13 -0
  26. data/spec/lib/active_record/rollout/acts_as_flaggable_spec.rb +31 -0
  27. data/spec/lib/active_record/rollout/feature_spec.rb +235 -0
  28. data/spec/lib/active_record/rollout/flag_spec.rb +8 -0
  29. data/spec/lib/active_record/rollout/flaggable_flag_spec.rb +8 -0
  30. data/spec/lib/active_record/rollout/flaggable_spec.rb +149 -0
  31. data/spec/lib/active_record/rollout/group_flag_spec.rb +7 -0
  32. data/spec/lib/active_record/rollout/opt_out_flag_spec.rb +8 -0
  33. data/spec/lib/active_record/rollout/percentage_flag_spec.rb +10 -0
  34. data/spec/lib/tasks/rollout_rake_spec.rb +162 -0
  35. data/spec/spec_helper.rb +40 -0
  36. data/spec/support/schema.rb +13 -0
  37. data/spec/support/shared_contexts/rake.rb +20 -0
  38. 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,7 @@
1
+ # A group of flaggable records of a given class may be flagged into a feature
2
+ # with this class.
3
+ class ActiveRecord::Rollout::GroupFlag < ActiveRecord::Rollout::Flag
4
+ validates :group_name, presence: true
5
+
6
+ attr_accessible :group_name
7
+ 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,5 @@
1
+ module ActiveRecord
2
+ class Rollout
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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,5 @@
1
+ ActiveRecord::Rollout.configure do |config|
2
+ # config.define_user_group :admins do |user|
3
+ # user.admin?
4
+ # end
5
+ 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