validation_issues 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 (67) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +50 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/validation_issues.js +2 -0
  5. data/app/assets/stylesheets/scaffold.css +56 -0
  6. data/app/assets/stylesheets/validation_issues.css +5 -0
  7. data/app/assets/stylesheets/validation_issues/buttons.css +77 -0
  8. data/app/controllers/validation_admin/validation_issues_controller.rb +44 -0
  9. data/app/models/validation_issue.rb +26 -0
  10. data/app/views/validation_admin/validation_issues/index.html.erb +25 -0
  11. data/app/views/validation_admin/validation_issues/show.html.erb +22 -0
  12. data/config/routes.rb +5 -0
  13. data/db/migrate/20120722204204_create_validation_issues.rb +16 -0
  14. data/lib/generators/valid_issues/templates/migration.rb +22 -0
  15. data/lib/generators/valid_issues/valid_issues_generator.rb +23 -0
  16. data/lib/tasks/validation_issues_tasks.rake +4 -0
  17. data/lib/validation_issues.rb +7 -0
  18. data/lib/validation_issues/acts_as_validation_issues.rb +37 -0
  19. data/lib/validation_issues/core_ext.rb +0 -0
  20. data/lib/validation_issues/engine.rb +4 -0
  21. data/lib/validation_issues/version.rb +3 -0
  22. data/test/acts_as_validation_issues_test.rb +21 -0
  23. data/test/dummy/README.rdoc +261 -0
  24. data/test/dummy/Rakefile +7 -0
  25. data/test/dummy/app/assets/javascripts/application.js +15 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  27. data/test/dummy/app/controllers/application_controller.rb +3 -0
  28. data/test/dummy/app/helpers/application_helper.rb +2 -0
  29. data/test/dummy/app/models/user.rb +10 -0
  30. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  31. data/test/dummy/config.ru +4 -0
  32. data/test/dummy/config/application.rb +56 -0
  33. data/test/dummy/config/boot.rb +10 -0
  34. data/test/dummy/config/database.yml +25 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +37 -0
  37. data/test/dummy/config/environments/production.rb +67 -0
  38. data/test/dummy/config/environments/test.rb +37 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/inflections.rb +15 -0
  41. data/test/dummy/config/initializers/mime_types.rb +5 -0
  42. data/test/dummy/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy/config/initializers/session_store.rb +8 -0
  44. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/test/dummy/config/locales/en.yml +5 -0
  46. data/test/dummy/config/routes.rb +58 -0
  47. data/test/dummy/db/development.sqlite3 +0 -0
  48. data/test/dummy/db/migrate/20120722183345_create_users.rb +11 -0
  49. data/test/dummy/db/schema.rb +38 -0
  50. data/test/dummy/db/test.sqlite3 +0 -0
  51. data/test/dummy/log/development.log +128 -0
  52. data/test/dummy/log/test.log +1129 -0
  53. data/test/dummy/public/404.html +26 -0
  54. data/test/dummy/public/422.html +26 -0
  55. data/test/dummy/public/500.html +25 -0
  56. data/test/dummy/public/favicon.ico +0 -0
  57. data/test/dummy/script/rails +6 -0
  58. data/test/dummy/test/fixtures/users.yml +11 -0
  59. data/test/dummy/test/unit/user_test.rb +7 -0
  60. data/test/factories/validation_issues.rb +9 -0
  61. data/test/functional/validation_issues_controller_test.rb +42 -0
  62. data/test/integration/navigation_test.rb +10 -0
  63. data/test/test_helper.rb +17 -0
  64. data/test/unit/helpers/validation_issues_helper_test.rb +4 -0
  65. data/test/unit/validation_issue_test.rb +23 -0
  66. data/test/validation_issues_test.rb +7 -0
  67. metadata +186 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,50 @@
1
+ = ValidationIssues
2
+
3
+ The Reason for this project is simple. People are paying attention to A/B tests and user conversion without having all the details.
4
+
5
+ For example, Lets say you have a form that appears to have users drop out of your signup funnel. Normally the first stab at the problem is to create an A/B test without any data.
6
+
7
+ What if you knew 90% of the people that hit the form had a validation error on the first_name field. I'm betting that you might not need an AB test. If you did add an AB test you would focus on the first_name field.
8
+
9
+ This problem actually happened on a live production app I was working on. Turns out the first_name validated white space. If a user added a space at the end of their name the validation would fail. The user would receive a "Invalid First Name" message. They would look at their name and be confused and eventually leave. The solution was as simple as sanitizing the first_name before validations. If we had this tool the problem would have never lasted for so long.
10
+
11
+ = Getting started
12
+
13
+ add this to your gemfile
14
+
15
+ gem 'validation_issues'
16
+
17
+ In your application_controller.rb add the following to have permissions to see the admin section:
18
+
19
+ class ApplicationController < ActionController::Base
20
+ protected
21
+ def has_validation_issues_admin_privileges
22
+ # logic you want to make this true
23
+ # Typically this will work
24
+ current_user && current_user.admin?
25
+ end
26
+ end
27
+
28
+ In your model add 'acts_as_validation_issues'
29
+
30
+ class User < ActiveRecord::Base
31
+ # add this line
32
+ acts_as_validation_issues
33
+ end
34
+
35
+ Now the only you need to do is this in your controller:
36
+
37
+ def create
38
+ user = User.new(:first_name => '', :last_name => 'Good Last Name')
39
+ if user.save
40
+ # do something
41
+ else
42
+ user.log_validation_issue!('user_form')
43
+ # do something else(like render the form again)
44
+ end
45
+ end
46
+
47
+ you pass the form's name into the user.log_validation_issue! method. This way if you have an AB test you can see the difference in the results.
48
+
49
+ Happy Coding. If you have any problems feel free to ask me directly, David Henner
50
+
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ValidationIssues'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,5 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
5
+ //= require_tree ./validation_issues
@@ -0,0 +1,77 @@
1
+ .button {
2
+ display: inline-block;
3
+ zoom: 1; /* zoom and *display = ie7 hack for display:inline-block */
4
+ *display: inline;
5
+ vertical-align: baseline;
6
+ margin: 0 2px;
7
+ outline: none;
8
+ cursor: pointer;
9
+ text-align: center;
10
+ text-decoration: none;
11
+ font: 14px/100% Arial, Helvetica, sans-serif;
12
+ padding: .5em 2em .55em;
13
+ text-shadow: 0 1px 1px rgba(0,0,0,.3);
14
+ -webkit-border-radius: .25em;
15
+ -moz-border-radius: .25em;
16
+ border-radius: .25em;
17
+ -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.2);
18
+ -moz-box-shadow: 0 1px 1px rgba(0,0,0,.2);
19
+ box-shadow: 0 1px 1px rgba(0,0,0,.2);
20
+ }
21
+
22
+ input[type="submit"].button {border: 0;background-color: transparent;color: #888;}
23
+ input[type="submit"].button.dark-orange-button {color:#FFF;}
24
+ input[type="submit"].button.small-dark-orange-button {color:#FFF;}
25
+
26
+ .dark-orange-button {
27
+ color: #fef4e9;
28
+ border: solid 1px #db3909;
29
+ background: #c6481a;
30
+ background: -webkit-gradient(linear, left top, left bottom, from(#c8581a), to(#c5441F));
31
+ background: -moz-linear-gradient(top, #c8581a, #c5441F);
32
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c8581a', endColorstr='#c5441F');
33
+ }
34
+ .dark-orange-button:hover {
35
+ background: #c4421F;
36
+ background: -webkit-gradient(linear, left top, left bottom, from(#c75114), to(#c23415));
37
+ background: -moz-linear-gradient(top, #c75114, #c23415);
38
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c75114', endColorstr='#c23415');
39
+ }
40
+ .dark-orange-button:active {
41
+ background: #c4421F;
42
+ background: -webkit-gradient(linear, left top, left bottom, from(#c75114), to(#c23415));
43
+ background: -moz-linear-gradient(top, #c75114, #c23415);
44
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c75114', endColorstr='#c23415');
45
+ }
46
+
47
+ .small-dark-orange-button {
48
+ font-family:'Century Schoolbook Italic', 'Century Schoolbook', Georgia;
49
+ color: #fef4e9 !important;
50
+ border: solid 1px #db3909;
51
+ background: #c6481a;
52
+ background: -webkit-gradient(linear, left top, left bottom, from(#c9681a), to(#c6451F));
53
+ background: -moz-linear-gradient(top, #c9681a, #c6451F);
54
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c9681a', endColorstr='#c6451F');
55
+ font-size: 14px;
56
+ font-weight: normal;
57
+ padding: 0.2em 0.7em;
58
+ &.myriad {
59
+ font-family: "MyriadPro","Lucida Sans Unicode", "Lucida Grande", sans-serif;
60
+ padding: 0.7em;
61
+ padding-top: 0.3em;
62
+ padding-bottom: 0.1em;
63
+ }
64
+ }
65
+ .small-dark-orange-button:hover {
66
+ color: #fff7ef;
67
+ background: #c4421F;
68
+ background: -webkit-gradient(linear, left top, left bottom, from(#c65114), to(#c23415));
69
+ background: -moz-linear-gradient(top, #c65114, #c23415);
70
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c65114', endColorstr='#c23415');
71
+ }
72
+ .small-dark-orange-button:active {
73
+ background: #c4421F;
74
+ background: -webkit-gradient(linear, left top, left bottom, from(#c65114), to(#c23415));
75
+ background: -moz-linear-gradient(top, #c65114, #c23415);
76
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#c65114', endColorstr='#c23415');
77
+ }
@@ -0,0 +1,44 @@
1
+ class ValidationAdmin::ValidationIssuesController < ApplicationController
2
+ before_filter :redirect_if_no_privileges
3
+ # has_validation_issues_admin_privileges
4
+
5
+ # GET /validation_issues
6
+ # GET /validation_issues.json
7
+ def index
8
+ @validation_issues = ValidationIssue.all
9
+
10
+ respond_to do |format|
11
+ format.html # index.html.erb
12
+ format.json { render json: @validation_issues }
13
+ end
14
+ end
15
+
16
+ # GET /validation_issues/1
17
+ # GET /validation_issues/1.json
18
+ def show
19
+ @validation_issue = ValidationIssue.find(params[:id])
20
+
21
+ respond_to do |format|
22
+ format.html # show.html.erb
23
+ format.json { render json: @validation_issue }
24
+ end
25
+ end
26
+
27
+ # DELETE /validation_issues/1
28
+ # DELETE /validation_issues/1.json
29
+ def destroy
30
+ @validation_issue = ValidationIssue.find(params[:id])
31
+ @validation_issue.clear_data!
32
+
33
+ respond_to do |format|
34
+ format.html { redirect_to validation_admin_validation_issues_url }
35
+ format.json { head :no_content }
36
+ end
37
+ end
38
+
39
+ protected
40
+
41
+ def redirect_if_no_privileges
42
+ redirect_to root_url and return unless has_validation_issues_admin_privileges
43
+ end
44
+ end
@@ -0,0 +1,26 @@
1
+ class ValidationIssue < ActiveRecord::Base
2
+ attr_accessible :form_name, :issue_count, :issue_hash, :issue_type, :notes
3
+ serialize :issue_hash, Hash
4
+
5
+ validates :form_name, :presence => true, :uniqueness => true
6
+
7
+ def clear_data!
8
+ self.issue_count = 0
9
+ self.issue_hash = {}
10
+ self.notes = ''
11
+ self.save!
12
+ end
13
+
14
+ def increment_issue_count!
15
+ increment_with_sql!('issue_count')
16
+ end
17
+
18
+ protected
19
+
20
+ def increment_with_sql!(attribute, by = 1)
21
+ raise ArgumentError("Invalid attribute: #{attribute}") unless attribute_names.include?(attribute.to_s)
22
+ original_value_sql = "CASE WHEN `#{attribute}` IS NULL THEN 0 ELSE `#{attribute}` END"
23
+ self.class.update_all("`#{attribute}` = #{original_value_sql} + #{by.to_i}", "id = #{id}")
24
+ reload
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <h1> Validation Issues </h1>
4
+
5
+ <table>
6
+ <tr>
7
+ <th>Type</th>
8
+ <th>Form Name</th>
9
+ <th>Count</th>
10
+ <th></th>
11
+ <th></th>
12
+ </tr>
13
+
14
+ <% @validation_issues.each do |validation_issue| %>
15
+ <tr>
16
+ <td><%= validation_issue.issue_type %></td>
17
+ <td><%= validation_issue.form_name %></td>
18
+ <td><%= validation_issue.issue_count %></td>
19
+ <td><%= link_to 'Show', validation_admin_validation_issue_path(validation_issue) %></td>
20
+ <td><%= link_to 'Reset!',
21
+ validation_admin_validation_issue_path(validation_issue),
22
+ method: :delete, data: { confirm: 'Are you sure?' } %></td>
23
+ </tr>
24
+ <% end %>
25
+ </table>
@@ -0,0 +1,22 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <h2> <%= @validation_issue.issue_type %>: <%= @validation_issue.form_name %> </h2>
4
+
5
+ <p>
6
+ <b>Issues (<%= @validation_issue.issue_count %>):</b>
7
+ <ul>
8
+ <% @validation_issue.issue_hash.each do |k, v| %>
9
+ <li><label> <%= k %>: </label> <%= v %></li>
10
+ <% end %>
11
+ </ul>
12
+
13
+ </p>
14
+
15
+ <% unless @validation_issue.notes.blank? %>
16
+ <p>
17
+ <b>Notes:</b>
18
+ <%= @validation_issue.notes %>
19
+ </p>
20
+ <% end %>
21
+
22
+ <%= link_to 'Back', validation_admin_validation_issues_path, :class => 'button small-dark-orange-button' %>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ namespace :validation_admin do
3
+ resources :validation_issues
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class CreateValidationIssues < ActiveRecord::Migration
2
+ def change
3
+ create_table :validation_issues do |t|
4
+ t.string :issue_type
5
+ t.string :form_name, :null => false
6
+ t.text :issue_hash, :null => false
7
+ t.text :notes, :default => ''
8
+ t.integer :issue_count
9
+
10
+ t.timestamps
11
+ end
12
+ add_index :validation_issues, :form_name# , :length => 10
13
+ add_index :validation_issues, :issue_type
14
+ add_index :validation_issues, [:form_name, :issue_type], :unique => true
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ class CreateValidationIssues < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :validation_issues do |t|
4
+
5
+ t.string :issue_type, :null => false
6
+ t.string :form_name, :null => false
7
+ t.text :issue_hash, :null => false
8
+ #t.text :issued_by_ids
9
+ t.text :notes
10
+ t.integer :issue_count
11
+
12
+ t.timestamps
13
+ end
14
+ add_index :validation_issues, :form_name
15
+ add_index :validation_issues, :issue_type
16
+
17
+ end
18
+
19
+ def self.down
20
+ drop_table :versions
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # CURRENT FILE :: lib/generators/valid_issues/valid_issues_generator.rb
2
+ # Requires
3
+ require 'rails/generators'
4
+ require 'rails/generators/migration'
5
+
6
+ class ValidIssuesGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ def self.source_root
9
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
10
+ end
11
+
12
+ def self.next_migration_number(dirname)
13
+ if ActiveRecord::Base.timestamped_migrations
14
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
15
+ else
16
+ "%.3d" % (current_migration_number(dirname) + 1)
17
+ end
18
+ end
19
+
20
+ def create_migration_file
21
+ migration_template 'migration.rb', 'db/migrate/create_validation_issues_table.rb'
22
+ end
23
+ end