detour 0.0.3 → 0.0.5

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +2 -25
  3. data/app/assets/javascripts/detour/add_fields.js +7 -0
  4. data/app/assets/javascripts/detour/delete_feature.js +9 -11
  5. data/app/assets/javascripts/detour/delete_flag.js +9 -11
  6. data/app/assets/javascripts/detour/delete_group.js +5 -0
  7. data/app/assets/javascripts/detour/feature_lines.js +23 -0
  8. data/app/assets/javascripts/detour/modals.js +1 -0
  9. data/app/assets/stylesheets/detour/main.css +12 -0
  10. data/app/controllers/detour/features_controller.rb +3 -2
  11. data/app/controllers/detour/flaggable_flags_controller.rb +9 -77
  12. data/app/controllers/detour/groups_controller.rb +39 -0
  13. data/app/helpers/detour/application_helper.rb +11 -0
  14. data/app/helpers/detour/flaggable_flags_helper.rb +20 -0
  15. data/app/helpers/detour/flags_helper.rb +5 -1
  16. data/app/models/detour/concerns/keepable.rb +21 -0
  17. data/app/models/detour/concerns/matchers.rb +28 -9
  18. data/app/models/detour/database_group_flag.rb +30 -0
  19. data/app/models/detour/defined_group.rb +21 -0
  20. data/app/models/detour/defined_group_flag.rb +28 -0
  21. data/app/models/detour/feature.rb +4 -3
  22. data/app/models/detour/flag_in_flag.rb +1 -8
  23. data/app/models/detour/flaggable_flag.rb +24 -0
  24. data/app/models/detour/group.rb +17 -0
  25. data/app/models/detour/membership.rb +41 -0
  26. data/app/models/detour/opt_out_flag.rb +1 -8
  27. data/app/views/detour/flaggable_flags/_flaggable_flag_fields.html.erb +19 -0
  28. data/app/views/detour/flaggable_flags/index.html.erb +13 -26
  29. data/app/views/detour/flags/_feature_form.html.erb +12 -3
  30. data/app/views/detour/flags/index.html.erb +7 -2
  31. data/app/views/detour/groups/_group.html.erb +3 -0
  32. data/app/views/detour/groups/_membership_fields.html.erb +19 -0
  33. data/app/views/detour/groups/index.html.erb +21 -0
  34. data/app/views/detour/groups/show.html.erb +41 -0
  35. data/app/views/detour/memberships/_membership.html.erb +4 -0
  36. data/app/views/detour/{features → shared}/_errors.html.erb +2 -2
  37. data/app/views/detour/shared/_nav.html.erb +1 -0
  38. data/app/views/detour/shared/error.js.erb +5 -0
  39. data/config/locales/en.yml +11 -0
  40. data/config/routes.rb +8 -7
  41. data/detour.gemspec +1 -0
  42. data/lib/detour/acts_as_flaggable.rb +19 -4
  43. data/lib/detour/configuration.rb +1 -1
  44. data/lib/detour/flag_form.rb +53 -34
  45. data/lib/detour/flaggable.rb +0 -19
  46. data/lib/detour/version.rb +1 -1
  47. data/lib/generators/templates/migration.rb +21 -1
  48. data/lib/tasks/.gitkeep +0 -0
  49. data/spec/controllers/detour/flaggable_flags_controller_spec.rb +30 -67
  50. data/spec/controllers/detour/groups_controller_spec.rb +107 -0
  51. data/spec/dummy/db/migrate/20131221052201_setup_detour.rb +21 -1
  52. data/spec/dummy/db/schema.rb +20 -1
  53. data/spec/factories/database_group_flag.rb +7 -0
  54. data/spec/factories/{group_flag.rb → defined_group_flag.rb} +1 -1
  55. data/spec/factories/group.rb +10 -0
  56. data/spec/factories/membership.rb +6 -0
  57. data/spec/features/database_group_flags_spec.rb +50 -0
  58. data/spec/features/database_groups_spec.rb +174 -0
  59. data/spec/features/defined_group_flags_spec.rb +67 -0
  60. data/spec/features/features_spec.rb +44 -0
  61. data/spec/features/flag_in_flags_spec.rb +22 -60
  62. data/spec/features/opt_out_flags_spec.rb +34 -59
  63. data/spec/integration/group_rollout_spec.rb +2 -2
  64. data/spec/lib/detour/acts_as_flaggable_spec.rb +12 -3
  65. data/spec/lib/detour/configuration_spec.rb +6 -2
  66. data/spec/lib/detour/flag_form_spec.rb +0 -11
  67. data/spec/lib/detour/flaggable_spec.rb +1 -54
  68. data/spec/models/detour/database_group_flag_spec.rb +29 -0
  69. data/spec/models/detour/defined_group_spec.rb +21 -0
  70. data/spec/models/detour/feature_spec.rb +57 -119
  71. data/spec/models/detour/flag_in_flag_spec.rb +1 -4
  72. data/spec/models/detour/flaggable_flag_spec.rb +25 -0
  73. data/spec/models/detour/group_flag_spec.rb +1 -1
  74. data/spec/models/detour/membership_spec.rb +58 -0
  75. data/spec/models/detour/opt_out_flag_spec.rb +1 -4
  76. data/spec/spec_helper.rb +4 -0
  77. metadata +97 -81
  78. data/app/models/detour/concerns/flag_actions.rb +0 -141
  79. data/app/models/detour/group_flag.rb +0 -13
  80. data/app/views/detour/features/_success.html.erb +0 -1
  81. data/app/views/detour/features/error.js.erb +0 -5
  82. data/lib/tasks/detour.rake +0 -119
  83. data/spec/features/group_flags_spec.rb +0 -49
  84. data/spec/integration/flag_rollout_spec.rb +0 -27
  85. data/spec/lib/tasks/detour_spec.rb +0 -162
  86. /data/app/views/detour/{features → shared}/success.js.erb +0 -0
@@ -3,6 +3,17 @@ require "spec_helper"
3
3
  describe "listing features for a type" do
4
4
  let!(:feature) { create :feature }
5
5
 
6
+ before do
7
+ ENV["DETOUR_GITHUB_REPO"] = "jclem/detour"
8
+ ENV["DETOUR_GITHUB_BRANCH"] = "foo"
9
+ end
10
+
11
+ after do
12
+ ENV["DETOUR_GITHUB_REPO"] = nil
13
+ ENV["DETOUR_GITHUB_BRANCH"] = nil
14
+ end
15
+
16
+
6
17
  before do
7
18
  Detour.config.grep_dirs = %w[spec/dummy/app/**/*.{rb,erb}]
8
19
  visit "/detour/flags/users"
@@ -15,6 +26,39 @@ describe "listing features for a type" do
15
26
  it "lists features found in the codebase" do
16
27
  page.should have_content "show_widget_table"
17
28
  end
29
+
30
+ describe "feature check line numbers" do
31
+ let!(:feature) { create :feature, name: "not-used" }
32
+
33
+ context "when the feature has no lines" do
34
+ it "gets a ban circle" do
35
+ within "tr#feature_1" do
36
+ page.should have_selector "i.glyphicon-ban-circle"
37
+ end
38
+ end
39
+ end
40
+
41
+ context "when the feature has liens" do
42
+ it "gets a check mark" do
43
+ within "tr#new_feature" do
44
+ page.should have_selector "i.glyphicon-ok"
45
+ end
46
+ end
47
+
48
+ it "displays its count" do
49
+ within "tr#new_feature" do
50
+ page.should have_content "(1 use)"
51
+ end
52
+ end
53
+
54
+ describe "clicking the check mark", js: true do
55
+ it "links to the line on GitHub" do
56
+ page.find("i.glyphicon-ok").click
57
+ page.should have_link "spec/dummy/app/views/application/index.html.erb#L1", href: "https://github.com/jclem/detour/blob/foo/spec/dummy/app/views/application/index.html.erb#L1"
58
+ end
59
+ end
60
+ end
61
+ end
18
62
  end
19
63
 
20
64
  describe "creating a new feature", js: true do
@@ -23,73 +23,43 @@ describe "listing flag_in_flags" do
23
23
  end
24
24
 
25
25
  it "displays the flagged-in model's find-by" do
26
- page.should have_content flag.flaggable.email
26
+ page.find("input[type='text'][disabled]").value.should eq flag.flaggable.email
27
27
  end
28
28
  end
29
29
 
30
- describe "creating flag-ins", js: true do
30
+ describe "creating a flag-in", js: true do
31
31
  let(:user) { create :user }
32
32
  let!(:feature) { create :feature }
33
33
 
34
34
  before do
35
35
  User.instance_variable_set "@detour_flaggable_find_by", :email
36
36
  visit "/detour/flag-ins/#{feature.name}/users"
37
- page.find("[data-target='#create-flaggable-flag']").click
37
+ page.find(".add-fields").click
38
38
  end
39
39
 
40
- context "when creating multiple flag-ins" do
41
- let(:user2) { create :user, email: "another_user@example.com" }
42
-
43
- context "when successful" do
44
- before do
45
- fill_in "ids", with: [user.email, user2.email].join(",")
46
- click_button "Create Flag-in"
47
- end
48
-
49
- it "displays a success message" do
50
- page.should have_content "Users #{user.email}, #{user2.email} have been flagged in to #{feature.name}"
51
- end
40
+ context "when successful" do
41
+ before do
42
+ name = page.find("##{page.all("label")[-2][:for]}")[:name]
43
+ fill_in name, with: user.email
44
+ click_button "Update Flag-ins"
52
45
  end
53
46
 
54
- context "when unsuccessful" do
55
- before do
56
- fill_in "ids", with: "#{user.email},foo"
57
- click_button "Create Flag-in"
58
- end
47
+ it "displays a flash message" do
48
+ page.should have_content "Your flag-ins have been updated"
49
+ end
59
50
 
60
- it "displays error messages" do
61
- page.should have_content "Couldn't find User with email = foo"
62
- end
51
+ it "shows the newly added flag-in" do
52
+ page.find("input[type='text'][disabled]").value.should eq user.email
63
53
  end
64
54
  end
65
55
 
66
- context "when creating single flag-ins" do
67
- context "when successful" do
68
- before do
69
- fill_in "ids", with: user.email
70
- click_button "Create Flag-in"
71
- end
72
-
73
- it "displays a success message" do
74
- page.should have_content "User #{user.email} has been flagged in to #{feature.name}"
75
- end
76
-
77
- it "renders the new flag-in" do
78
- within "table" do
79
- page.should have_content user.email
80
- end
81
- end
56
+ context "when unsuccessful" do
57
+ before do
58
+ click_button "Update Flag-in"
82
59
  end
83
60
 
84
- context "when unsuccessful" do
85
- before do
86
- fill_in "ids", with: "foo"
87
- click_button "Create Flag-in"
88
- end
89
-
90
- it "displays error messages" do
91
- page.should have_content "Couldn't find User with email = foo"
92
- end
61
+ it "displays error messages" do
62
+ page.should have_content "Users flag ins user \"\" could not be found"
93
63
  end
94
64
  end
95
65
  end
@@ -99,20 +69,12 @@ describe "destroying flag-ins", js: true do
99
69
 
100
70
  before do
101
71
  visit "/detour/flag-ins/#{flag.feature.name}/users"
102
- page.find(".delete-flag").click
103
- click_link "Delete Flag-in"
72
+ name = page.find("##{page.all("label").last[:for]}")[:name]
73
+ check name
74
+ click_button "Update Flag-in"
104
75
  end
105
76
 
106
- it "displays a flash message" do
107
- page.should have_content "#{flag.feature.name} flag-in for User #{flag.flaggable.send flag.flaggable_type.constantize.detour_flaggable_find_by} has been deleted."
108
- end
109
-
110
- it "destroys the flag-in" do
111
- expect { flag.reload }.to raise_error ActiveRecord::RecordNotFound
112
- end
113
-
114
-
115
77
  it "removes the flag from the list" do
116
- page.should_not have_content flag.flaggable.email
78
+ page.should_not have_selector "label[for='feature_flag_in_flags_attributes_0_flaggable_key']"
117
79
  end
118
80
  end
@@ -14,69 +14,52 @@ describe "counting opt out flags" do
14
14
  end
15
15
  end
16
16
 
17
- describe "creating opt-outs", js: true do
17
+ describe "listing opt_out_flags" do
18
+ let!(:flag) { create :opt_out_flag }
19
+
20
+ before do
21
+ User.instance_variable_set "@detour_flaggable_find_by", :email
22
+ visit "/detour/opt-outs/#{flag.feature.name}/users"
23
+ end
24
+
25
+ it "displays the opted-out model's find-by" do
26
+ page.find("input[type='text'][disabled]").value.should eq flag.flaggable.email
27
+ end
28
+ end
29
+
30
+ describe "creating a opt-out", js: true do
18
31
  let(:user) { create :user }
19
32
  let!(:feature) { create :feature }
20
33
 
21
34
  before do
22
35
  User.instance_variable_set "@detour_flaggable_find_by", :email
23
36
  visit "/detour/opt-outs/#{feature.name}/users"
24
- page.find("[data-target='#create-flaggable-flag']").click
37
+ page.find(".add-fields").click
25
38
  end
26
39
 
27
- context "when creating multiple opt-outs" do
28
- let(:user2) { create :user, email: "another_user@example.com" }
29
-
30
- context "when successful" do
31
- before do
32
- fill_in "ids", with: [user.email, user2.email].join(",")
33
- click_button "Create Opt-out"
34
- end
35
-
36
- it "displays a success message" do
37
- page.should have_content "Users #{user.email}, #{user2.email} have been opted out of #{feature.name}"
38
- end
40
+ context "when successful" do
41
+ before do
42
+ name = page.find("##{page.all("label")[-2][:for]}")[:name]
43
+ fill_in name, with: user.email
44
+ click_button "Update Opt-outs"
39
45
  end
40
46
 
41
- context "when unsuccessful" do
42
- before do
43
- fill_in "ids", with: "#{user.email},foo"
44
- click_button "Create Opt-out"
45
- end
47
+ it "displays a flash message" do
48
+ page.should have_content "Your opt-outs have been updated"
49
+ end
46
50
 
47
- it "displays error messages" do
48
- page.should have_content "Couldn't find User with email = foo"
49
- end
51
+ it "shows the newly added opt-out" do
52
+ page.find("input[type='text'][disabled]").value.should eq user.email
50
53
  end
51
54
  end
52
55
 
53
- context "when creating single opt-outs" do
54
- context "when successful" do
55
- before do
56
- fill_in "ids", with: user.email
57
- click_button "Create Opt-out"
58
- end
59
-
60
- it "displays a success message" do
61
- page.should have_content "User #{user.email} has been opted out of #{feature.name}"
62
- end
63
-
64
- it "renders the new opt-out" do
65
- within "table" do
66
- page.should have_content user.email
67
- end
68
- end
56
+ context "when unsuccessful" do
57
+ before do
58
+ click_button "Update Opt-outs"
69
59
  end
70
60
 
71
- context "when unsuccessful" do
72
- before do
73
- fill_in "ids", with: "foo"
74
- click_button "Create Opt-out"
75
- end
76
-
77
- it "displays error messages" do
78
- page.should have_content "Couldn't find User with email = foo"
79
- end
61
+ it "displays error messages" do
62
+ page.should have_content "Users opt outs user \"\" could not be found"
80
63
  end
81
64
  end
82
65
  end
@@ -86,20 +69,12 @@ describe "destroying opt-outs", js: true do
86
69
 
87
70
  before do
88
71
  visit "/detour/opt-outs/#{flag.feature.name}/users"
89
- page.find(".delete-flag").click
90
- click_link "Delete Opt-out"
72
+ name = page.find("##{page.all("label").last[:for]}")[:name]
73
+ check name
74
+ click_button "Update Opt-outs"
91
75
  end
92
76
 
93
- it "displays a flash message" do
94
- page.should have_content "#{flag.feature.name} opt-out for User #{flag.flaggable.send flag.flaggable_type.constantize.detour_flaggable_find_by} has been deleted."
95
- end
96
-
97
- it "destroys the opt-out" do
98
- expect { flag.reload }.to raise_error ActiveRecord::RecordNotFound
99
- end
100
-
101
-
102
77
  it "removes the flag from the list" do
103
- page.should_not have_content flag.flaggable.email
78
+ page.should_not have_selector "label[for='feature_opt_out_flags_attributes_0_flaggable_key']"
104
79
  end
105
80
  end
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  describe "group rollouts" do
4
4
  let(:user) { User.create(name: "foo") }
5
5
  let(:feature) { Detour::Feature.create(name: "foo") }
6
- let!(:flag) { feature.group_flags.create(flaggable_type: "User", group_name: "foo_users") }
6
+ let!(:flag) { feature.defined_group_flags.create(flaggable_type: "User", group_name: "foo_users") }
7
7
 
8
8
  describe "creating a group rollout" do
9
9
  before do
@@ -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_groups?(user).should be_true
16
+ feature.match_defined_groups?(user).should be_true
17
17
  end
18
18
  end
19
19
  end
@@ -14,16 +14,25 @@ describe Detour::ActsAsFlaggable do
14
14
  describe "#acts_as_flaggable" do
15
15
  describe "Detour::Feature associations" do
16
16
  subject { Detour::Feature.new }
17
- it { should have_many(:users_group_flags).class_name("Detour::GroupFlag").dependent(:destroy) }
18
- it { should allow_mass_assignment_of(:users_group_flags_attributes) }
19
- it { should accept_nested_attributes_for(:users_group_flags) }
17
+ it { should have_many(:users_defined_group_flags).class_name("Detour::DefinedGroupFlag").dependent(:destroy) }
18
+ it { should allow_mass_assignment_of(:users_defined_group_flags_attributes) }
19
+ it { should accept_nested_attributes_for(:users_defined_group_flags) }
20
+
21
+ it { should have_many(:users_database_group_flags).class_name("Detour::DatabaseGroupFlag").dependent(:destroy) }
22
+ it { should allow_mass_assignment_of(:users_database_group_flags_attributes) }
23
+ it { should accept_nested_attributes_for(:users_database_group_flags)}
20
24
 
21
25
  it { should have_one(:users_percentage_flag).class_name("Detour::PercentageFlag").dependent(:destroy) }
22
26
  it { should allow_mass_assignment_of(:users_percentage_flag_attributes) }
23
27
  it { should accept_nested_attributes_for(:users_percentage_flag) }
24
28
 
25
29
  it { should have_many(:users_flag_ins).class_name("Detour::FlagInFlag").dependent(:destroy) }
30
+ it { should allow_mass_assignment_of(:users_flag_ins_attributes) }
31
+ it { should accept_nested_attributes_for(:users_flag_ins) }
32
+
26
33
  it { should have_many(:users_opt_outs).class_name("Detour::OptOutFlag").dependent(:destroy) }
34
+ it { should allow_mass_assignment_of(:users_opt_outs_attributes) }
35
+ it { should accept_nested_attributes_for(:users_opt_outs) }
27
36
  end
28
37
 
29
38
  context "when given a :find_by parameter" do
@@ -2,14 +2,18 @@ require "spec_helper"
2
2
 
3
3
  describe Detour::Configuration do
4
4
  describe ".define_group_for_class" do
5
- let(:block) { Proc.new {} }
5
+ let(:block) { Proc.new { "foo!" } }
6
6
 
7
7
  before do
8
8
  subject.send :define_group_for_class, "User", "user_id_1", &block
9
9
  end
10
10
 
11
11
  it "defines a group for the given class" do
12
- subject.defined_groups["User"].should eq({ "user_id_1" => block })
12
+ subject.defined_groups["User"].values[0].name.should eq "user_id_1"
13
+ end
14
+
15
+ it "assigns the test for the group" do
16
+ subject.defined_groups["User"].values[0].test(1).should eq "foo!"
13
17
  end
14
18
  end
15
19
 
@@ -15,17 +15,6 @@ describe Detour::FlagForm do
15
15
  end
16
16
  end
17
17
 
18
- describe "#group_names" do
19
- before do
20
- Detour.config.define_user_group :admins do |user|
21
- end
22
- end
23
-
24
- it "returns the name of defined groups" do
25
- subject.group_names.should eq ["admins"]
26
- end
27
- end
28
-
29
18
  describe "#update_attributes" do
30
19
  let(:features_params) do
31
20
  {
@@ -38,59 +38,6 @@ describe Detour::Flaggable do
38
38
  user.has_feature?(feature.name)
39
39
  end
40
40
 
41
- context "when given a block" do
42
- context "and the user is flagged in" do
43
- before do
44
- feature.flag_in_flags.create(flaggable: user)
45
- end
46
-
47
- it "calls the block" do
48
- foo = "foo"
49
- user.has_feature?(feature.name) { foo = "bar" }
50
- foo.should eq "bar"
51
- end
52
-
53
- it "returns the match" do
54
- foo = "foo"
55
-
56
- if user.has_feature? :not_feature do
57
- end; else
58
- foo = "bar"
59
- end
60
-
61
- foo.should eq "bar"
62
- end
63
-
64
- context "when the block raises an exception" do
65
- it "increments the failure_count of the feature" do
66
- begin
67
- user.has_feature? feature.name do
68
- raise "This is an exception"
69
- end
70
- rescue
71
- feature.reload.failure_count.should eq 1
72
- end
73
- end
74
-
75
- it "raises the exception" do
76
- expect do
77
- user.has_feature? feature.name do
78
- raise "This is an exception"
79
- end
80
- end.to raise_error "This is an exception"
81
- end
82
- end
83
- end
84
-
85
- context "and the user is not flagged in" do
86
- it "does not call the block" do
87
- foo = "foo"
88
- user.has_feature?(feature.name) { foo = "bar" }
89
- foo.should eq "foo"
90
- end
91
- end
92
- end
93
-
94
41
  context "when the user is not flagged in" do
95
42
  it "returns false" do
96
43
  user.has_feature?(feature.name).should be_false
@@ -136,7 +83,7 @@ describe Detour::Flaggable do
136
83
  _user.name == user.name
137
84
  end
138
85
 
139
- feature.group_flags.create(flaggable_type: "User", group_name: "name_foo")
86
+ feature.defined_group_flags.create(flaggable_type: "User", group_name: "name_foo")
140
87
  end
141
88
 
142
89
  it "returns true" do
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Detour::DatabaseGroupFlag do
4
+ it { should validate_presence_of :group_id }
5
+ it { should validate_presence_of :flaggable_type }
6
+ it { should validate_uniqueness_of(:feature_id).scoped_to(:group_id) }
7
+
8
+ it { should allow_mass_assignment_of :group_id }
9
+
10
+ it { should belong_to :group }
11
+ it { should have_many(:memberships).through(:group) }
12
+
13
+ describe "#members" do
14
+ let(:flag) { create :database_group_flag }
15
+ let!(:membership) { create :membership, group: flag.group }
16
+
17
+ it "returns its membership members" do
18
+ flag.members.should eq [membership.member]
19
+ end
20
+ end
21
+
22
+ describe "#group_name" do
23
+ let(:flag) { create :database_group_flag }
24
+
25
+ it "returns the name of the group" do
26
+ flag.group_name.should eq flag.group.name
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe Detour::Group do
4
+ it { should validate_presence_of :name }
5
+ it { should validate_presence_of :flaggable_type }
6
+ it { should validate_uniqueness_of(:name).scoped_to(:flaggable_type) }
7
+ it { should ensure_inclusion_of(:flaggable_type).in_array(Detour.config.flaggable_types) }
8
+
9
+ it { should accept_nested_attributes_for :memberships }
10
+
11
+ it { should have_many(:memberships).dependent(:destroy) }
12
+ it { should allow_mass_assignment_of :name }
13
+ it { should allow_mass_assignment_of :flaggable_type }
14
+
15
+ describe "#to_s" do
16
+ it "returns the group name" do
17
+ group = create :group
18
+ group.to_s.should eq group.name
19
+ end
20
+ end
21
+ end