detour 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf7115b71744ec2229a2d850f8b6a4efecd79d28
4
- data.tar.gz: 0f5afa558a4412a10db629f0c448d4647c0959b3
3
+ metadata.gz: 80d487ed6d4c933fbf39a7743ae7f14583c58335
4
+ data.tar.gz: b07a8fd7a7e3d6d430c1e7708edf77eae200b2de
5
5
  SHA512:
6
- metadata.gz: 8b37642c8dd68bd34f924dcc5438c847c9c4dde889a91e1d32afe5375b4de3aee2ff8d6c04c9b9fc34a892c7d79402cd5f7fdabb42bb4054ad15daa37152700e
7
- data.tar.gz: a2f4afa256166f2f97b12f52bb0853c60a9e9674d2f83dd34b214caf30a7736042706ca9f2479b790f4fabb77219ba006778e38eeec72abe4374857497cd106a
6
+ metadata.gz: cf2629eb8e3d045326c69997e05f4a009918ead2a123ef903359d8ac8a39837dfa00d8984ea3205771b5a8edbb49ea4f4411c3b76d7045041df255e1f6b56ddc
7
+ data.tar.gz: b40978ca2f160e271bbb7495ab26e81378b4e4da3ce7cc0e2a7c84c196d6c0bb3c96a51425d8485ef4a558d66b2629fe3076a4b468977f3ffe4a2eb3aa84ee33
@@ -1,8 +1,6 @@
1
1
  # Represents an individual feature that may be rolled out to a set of records
2
2
  # via individual flags, percentages, or defined groups.
3
3
  class Detour::Feature < ActiveRecord::Base
4
- include Detour::Concerns::Matchers
5
-
6
4
  self.table_name = :detour_features
7
5
 
8
6
  serialize :flag_in_counts, JSON
@@ -11,6 +11,17 @@ class Detour::Flag < ActiveRecord::Base
11
11
 
12
12
  attr_accessible :flaggable_type
13
13
 
14
+ scope :without_opt_outs, ->(record) {
15
+ where(flaggable_type: record.class.to_s).where <<-SQL
16
+ feature_id NOT IN (
17
+ SELECT feature_id FROM detour_flags
18
+ WHERE detour_flags.type = 'Detour::OptOutFlag'
19
+ AND detour_flags.flaggable_type = '#{record.class.to_s}'
20
+ AND detour_flags.flaggable_id = '#{record.id}'
21
+ )
22
+ SQL
23
+ }
24
+
14
25
  private
15
26
 
16
27
  def flag_type
@@ -9,22 +9,18 @@ module Detour::Flaggable
9
9
  # @return [Array] An array of {Detour::Feature}s.
10
10
  def features
11
11
  @features ||= begin
12
- @features = []
12
+ features = unfiltered_features
13
+ defined_group_flags = Detour::DefinedGroupFlag.without_opt_outs(self).where("feature_id IN (?)", features.map(&:id) << -1) # Prevents NOT IN (NULL)
13
14
 
14
- opt_out_ids = opt_out_flags.map(&:feature_id) + [-1] # prevents "NOT IN (NULL)"
15
- table_name = self.class.table_name
15
+ defined_group_flags.each do |defined_group_flag|
16
+ defined_group = Detour::DefinedGroup.by_type(self.class)[defined_group_flag.group_name]
16
17
 
17
- database_group_features = Detour::Feature.joins(:"#{table_name}_database_group_flags" => :memberships).where("detour_memberships" => { member_id: self.id }).where("'detour_features'.id NOT IN (?)", opt_out_ids)
18
- @features.concat database_group_features
18
+ unless defined_group && defined_group.test(self)
19
+ feeatures.delete defined_group_flag.feature
20
+ end
21
+ end
19
22
 
20
- defined_group_features = Detour::Feature.joins(:"#{table_name}_defined_group_flags").where("'detour_features'.id NOT IN (?)", opt_out_ids)
21
- @features.concat defined_group_features.select { |feature| feature.match_defined_groups?(self) }
22
-
23
- percentage_group_features = Detour::Feature.joins(:"#{table_name}_percentage_flag").where("'detour_features'.id NOT IN (?)", opt_out_ids)
24
- @features.concat percentage_group_features.select { |feature| feature.match_percentage?(self) }
25
-
26
- flag_in_features = Detour::Feature.joins(:"#{table_name}_flag_ins").where("'detour_features'.id NOT IN (?)", opt_out_ids)
27
- @features.concat flag_in_features
23
+ features
28
24
  end
29
25
  end
30
26
 
@@ -48,6 +44,50 @@ module Detour::Flaggable
48
44
  @detour_features ||= []
49
45
  end
50
46
 
47
+ private
48
+
49
+ def unfiltered_features
50
+ Detour::Feature.where(%Q{
51
+ detour_features.id IN (
52
+ -- Get features the record has been individually flagged in to
53
+ SELECT feature_id FROM detour_flags
54
+ WHERE detour_flags.type = 'Detour::FlagInFlag'
55
+ AND detour_flags.flaggable_type = '#{self.class.to_s}'
56
+ AND detour_flags.flaggable_id = '#{id}'
57
+ ) OR detour_features.id IN (
58
+ -- Get features the record has been flagged into via group membership
59
+ SELECT feature_id FROM detour_flags
60
+ INNER JOIN detour_groups
61
+ ON detour_groups.id = detour_flags.group_id
62
+ INNER JOIN detour_memberships
63
+ ON detour_memberships.group_id = detour_groups.id
64
+ AND detour_memberships.member_id = '#{id}'
65
+ WHERE detour_flags.type = 'Detour::DatabaseGroupFlag'
66
+ AND detour_flags.flaggable_type = '#{self.class}'
67
+ ) OR detour_features.id IN (
68
+ -- Get features the record has been flagged into via defined membership
69
+ -- We'll test them later
70
+ SELECT feature_id FROM detour_flags
71
+ WHERE detour_flags.type = 'Detour::DefinedGroupFlag'
72
+ AND detour_flags.flaggable_type = '#{self.class}'
73
+ ) OR detour_features.id IN (
74
+ -- Get features the record has been flagged into via percentage
75
+ SELECT feature_id FROM detour_flags
76
+ WHERE detour_flags.type = 'Detour::PercentageFlag'
77
+ AND detour_flags.flaggable_type = '#{self.class}'
78
+ AND '#{id}' % 10 < detour_flags.percentage / 10
79
+ )
80
+ }).where(%Q{
81
+ -- Exclude features the record has been opted out of.
82
+ detour_features.id NOT IN (
83
+ SELECT feature_id FROM detour_flags
84
+ WHERE detour_flags.type = 'Detour::OptOutFlag'
85
+ AND detour_flags.flaggable_type = '#{self.class}'
86
+ AND detour_flags.flaggable_id = '#{id}'
87
+ )
88
+ })
89
+ end
90
+
51
91
  included do
52
92
  # Finds a record by the field set by the :find_by param in
53
93
  # `acts_as_flaggable`. If no :find_by param was provided, :id is used.
@@ -1,3 +1,3 @@
1
1
  module Detour
2
- VERSION = "0.0.11"
2
+ VERSION = "0.0.12"
3
3
  end
@@ -13,7 +13,7 @@ describe "group rollouts" do
13
13
  end
14
14
 
15
15
  it "sets the feature on the user" do
16
- feature.match_defined_groups?(user).should be_true
16
+ user.features.should include feature
17
17
  end
18
18
  end
19
19
  end
@@ -7,7 +7,7 @@ describe "percentage rollouts" do
7
7
 
8
8
  describe "creating a percentage rollout" do
9
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
10
+ users.select { |user| user.features.include?(feature) }.length.should eq users.length / 5
11
11
  end
12
12
  end
13
13
  end
@@ -132,84 +132,4 @@ describe Detour::Feature do
132
132
  end
133
133
  end
134
134
  end
135
-
136
- describe "#match_percentage?" do
137
- let(:user) { create :user }
138
- let(:feature) { create :feature }
139
- let!(:flag) { create :percentage_flag, feature: feature, flaggable_type: user.class.to_s, percentage: 50 }
140
-
141
- context "when the user's ID matches `id % 10 < percentage / 10" do
142
- it "returns true" do
143
- user.stub(:id) { 1 }
144
- feature.match_percentage?(user).should be_true
145
- end
146
- end
147
-
148
- context "when the user's ID does not match `id % 10 < percentage / 10" do
149
- it "returns false" do
150
- user.stub(:id) { 5 }
151
- feature.match_percentage?(user).should be_false
152
- end
153
- end
154
- end
155
-
156
- describe "#match_database_group?" do
157
- let(:user) { create :user, name: "foo" }
158
- let(:user2) { create :user }
159
- let(:widget) { create :widget }
160
- let(:feature) { create :feature }
161
- let(:group) { create :group }
162
- let!(:membership) { create :membership, group: group, member: user }
163
- let!(:flag) { create :database_group_flag, feature: feature, flaggable_type: user.class.to_s, group: group }
164
-
165
- context "when the instance is in the group" do
166
- it "returns true" do
167
- feature.match_database_groups?(user).should be_true
168
- end
169
- end
170
-
171
- context "when the instance is not in the group" do
172
- it "returns false" do
173
- feature.match_database_groups?(user2).should be_false
174
- end
175
- end
176
-
177
- context "when the instance is not of the type of the group" do
178
- it "returns false" do
179
- feature.match_database_groups?(widget).should be_false
180
- end
181
- end
182
- end
183
-
184
- describe "#match_defined_groups?" do
185
- let(:user) { create :user, name: "foo" }
186
- let(:user2) { create :user }
187
- let(:widget) { create :widget }
188
- let(:feature) { create :feature }
189
- let!(:flag) { create :defined_group_flag, feature: feature, flaggable_type: user.class.to_s, group_name: "foo_users" }
190
-
191
- before do
192
- Detour.config.define_user_group "foo_users" do |user|
193
- user.name == "foo"
194
- end
195
- end
196
-
197
- context "when the instance matches the block" do
198
- it "returns true" do
199
- feature.match_defined_groups?(user).should be_true
200
- end
201
- end
202
-
203
- context "when the instance does not match the block" do
204
- it "returns false" do
205
- feature.match_defined_groups?(user2).should be_false
206
- end
207
- end
208
-
209
- context "when the instance is not of the type of the block" do
210
- it "returns false" do
211
- feature.match_defined_groups?(widget).should be_false
212
- end
213
- end
214
- end
215
135
  end
@@ -5,4 +5,25 @@ describe Detour::Flag do
5
5
  it { should validate_presence_of :feature }
6
6
  it { should validate_presence_of :flaggable_type }
7
7
  it { should allow_mass_assignment_of :flaggable_type }
8
+
9
+ describe ".without_opt_outs" do
10
+ let(:feature) { create :feature }
11
+ let(:feature2) { create :feature }
12
+ let(:user) { create :user }
13
+
14
+ before do
15
+ Detour.config.define_user_group("foo") { true }
16
+ Detour.config.define_user_group("bar") { true }
17
+ Detour.config.define_widget_group("baz") { true }
18
+
19
+ @flag1 = create :defined_group_flag, feature: feature, flaggable_type: "User", group_name: "foo"
20
+ @flag2 = create :defined_group_flag, feature: feature2, flaggable_type: "User", group_name: "bar"
21
+ @flag3 = create :defined_group_flag, feature: feature, flaggable_type: "Widget", group_name: "bar"
22
+ create :opt_out_flag, feature: feature2, flaggable: user
23
+ end
24
+
25
+ it "returns flags without opt outs" do
26
+ Detour::DefinedGroupFlag.without_opt_outs(user).should eq [@flag1]
27
+ end
28
+ end
8
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: detour
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Clem
@@ -245,7 +245,6 @@ files:
245
245
  - app/helpers/detour/flags_helper.rb
246
246
  - app/models/detour/concerns/countable_flag.rb
247
247
  - app/models/detour/concerns/keepable.rb
248
- - app/models/detour/concerns/matchers.rb
249
248
  - app/models/detour/database_group_flag.rb
250
249
  - app/models/detour/defined_group.rb
251
250
  - app/models/detour/defined_group_flag.rb
@@ -1,59 +0,0 @@
1
- module Detour::Concerns
2
- module Matchers
3
- # Determines whether or not the given instance has had the feature rolled out
4
- # to it via percentage.
5
- #
6
- # @example
7
- # feature.match_percentage?(current_user)
8
- #
9
- # @param [ActiveRecord::Base] instance A record to be tested for feature
10
- # rollout.
11
- #
12
- # @return Whether or not the given instance has the feature rolled out to it
13
- # via direct percentage.
14
- def match_percentage?(instance)
15
- flag = percentage_flags.find(:first, conditions: ["flaggable_type = ?", instance.class.to_s])
16
- percentage = flag ? flag.percentage : 0
17
-
18
- instance.id % 10 < percentage / 10
19
- end
20
-
21
- # Determines whether or not the given instance has had the feature rolled out
22
- # to it via database group membership.
23
- #
24
- # @example
25
- # feature.match_database_groups?(current_user)
26
- #
27
- # @param [ActiveRecord::Base] instance A record to be tested for feature
28
- # rollout.
29
- #
30
- # @return Whether or not the given instance has the feature rolled out to it
31
- # via direct database group membership.
32
- def match_database_groups?(instance)
33
- database_group_flags.where(flaggable_type: instance.class).map(&:members).flatten.uniq.include? instance
34
- end
35
-
36
- # Determines whether or not the given instance has had the feature rolled out
37
- # to it via defined group membership.
38
- #
39
- # @example
40
- # feature.match_defined_groups?(current_user)
41
- #
42
- # @param [ActiveRecord::Base] instance A record to be tested for feature
43
- # rollout.
44
- #
45
- # @return Whether or not the given instance has the feature rolled out to it
46
- # via direct group membership.
47
- def match_defined_groups?(instance)
48
- klass = instance.class.to_s
49
-
50
- return unless Detour::DefinedGroup.by_type(klass).any?
51
-
52
- group_names = defined_group_flags.find_all_by_flaggable_type(klass).collect(&:group_name)
53
-
54
- Detour::DefinedGroup.by_type(klass).collect { |name, group|
55
- group.test(instance) if group_names.include? group.name
56
- }.any?
57
- end
58
- end
59
- end