detour 0.0.3 → 0.0.5

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