detour 0.0.11 → 0.0.12

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 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