validation_rage 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - jruby
5
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in validation_rage.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Michael Bumann
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # ValidationRage
2
+
3
+ ValidationRage is a gem to capture validation errors from your Ruby application.
4
+ The goal is to identify usability issues in your application that are caused by validations.
5
+
6
+ Imaging a form with several field validations. ValidationRage makes it super easy to get information on what fields the users experience validation errors (and might leave because of that).
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'validation_rage'
13
+
14
+
15
+ ## Usage
16
+
17
+ By default ValidationRage will hook into ActiveRecord::Base and log all validation errors to your Rails log.
18
+
19
+ ValidationRage comes with an rails engine to configure your application. "config" in the following examples refer to Rails.application.config
20
+ You can configure if you want to hook into your models or controllers.
21
+
22
+ ### Logging on model level ###
23
+
24
+ You can hook ValidationRage into your classes that are ActiveModel::Validation compatible.
25
+
26
+ example configuration:
27
+
28
+ config.validation_rage.attach_to = [User, Account, Session] # hook into the User, Account, Session classes and attach an after_validation callback to track invalid records
29
+
30
+ ### Logging on controller level ###
31
+
32
+ The problem with attaching ValidationRage to your models is that you do not have any information about the context: where did the error happen? what parameters causes the error?
33
+ That's why you can hook ValidationRage into your controllers. ValidationRage will add an after_callback checking your instance variables for errors.
34
+
35
+ example configuration:
36
+
37
+ config.validation_rage.attach_to = ["users#create", "session#create"] # hook into the Users and Session controler create actions
38
+
39
+ # using * to match any action or any controller:
40
+
41
+ config.validation_rage.attach_to = ["*#create"] # hook into every create action. - this is done by adding the after_filter to the ApplicationController
42
+ config.validation_rage.attach_to = ["users#*"] # hook into every action in the users controler.
43
+
44
+ example configuration with both model and controller level logging:
45
+
46
+ config.validation_rage.attach_to = ["users#create", Company] # hook into the create action of the UsersController AND the Company model
47
+
48
+
49
+ ### Using different notifiers ###
50
+
51
+ ValidationRage uses ActiveSupport::Notifications to publish a notification message when an validation error happened. This makes it super easy to write a listener to those events and process the validation error information.
52
+ This gem comes with different Notifiers and since the integrate with ActiveSupport::Notifiers you can use several notifiers at the same time and write your own notifer.
53
+
54
+ #### LogNotifier ####
55
+
56
+ uses the logger to log your validation errors.
57
+
58
+ configuration:
59
+
60
+ config.validation_rage.notifier["Log"] = {:log_level => :warn, :logger => Logger.new("log/validations.log")}
61
+
62
+ #### UdpNotifier ####
63
+
64
+ send the errors as JSON to a UDP server server
65
+
66
+ configuration:
67
+
68
+ config.validation_rage.notifier["Udp"] = {:host => "localhost", :port => 3333}
69
+
70
+
71
+ #### ValidationRageNotifer ####
72
+
73
+ send the errors to the ValidationRage Application which gives you a nice accessible interface to analyze your data.
74
+ Signup at validationrage.com to get your API key
75
+
76
+ configuration:
77
+
78
+ config.validation_rage.notifier["ValidationRage"] = {:api_key => "<YOUR API KEY>"}
79
+
80
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+ task :default => :test
@@ -0,0 +1,18 @@
1
+ module ValidationRage
2
+ class BaseNotifier
3
+ class NotImplementedError < StandardError; end
4
+
5
+ def initialize(args={})
6
+ end
7
+ def call(event_name, payload)
8
+ raise NotImplementedError.new("your notifer must implement a call(event_name, payload) method")
9
+ end
10
+ def subscribe!
11
+ ActiveSupport::Notifications.subscribe(/validation_rage:.*/, self)
12
+ end
13
+
14
+ def data_present?(payload)
15
+ payload.values.first && !payload.values.first.empty?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module ValidationRage
2
+ module ControllerExtension
3
+
4
+ def notify_validation_rage
5
+ objects_with_errors = self.instance_variables.collect {|var_name| instance_variable_get(var_name) if validation_rage_errors_present?(var_name) }.compact
6
+ objects_with_errors.each do |o|
7
+ ActiveSupport::Notifications.publish("validation_rage:errors", {o.class.name => o.errors.to_hash, :context => self.validation_rage_context })
8
+ end
9
+ end
10
+
11
+ def validation_rage_errors_present?(var_name)
12
+ return false if var_name.to_s =~ /^@_/ # ignore variable names marked with "_" - those are by convention internal only
13
+ obj = instance_variable_get(var_name)
14
+ obj.respond_to?(:errors) && !obj.errors.empty?
15
+ end
16
+
17
+ def validation_rage_context
18
+ {
19
+ :controller => params[:controller],
20
+ :action => params[:action],
21
+ :request_path => request.path,
22
+ :params => request.filtered_parameters,
23
+ :request_uuid => request.uuid,
24
+ :referrer => request.referrer
25
+ }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ require "fnordmetric"
2
+ module ValidationRage
3
+ class FnordMetricNotifier < BaseNotifier
4
+ attr_accessor :fnord
5
+
6
+ def initialize(args={})
7
+ self.fnord = FnordMetric::API.new(:redis_url => args[:redis_url])
8
+ end
9
+
10
+ # I guess this is toooooo sloooow but anyhow let's play with it
11
+ def call(event_name, payload)
12
+ return unless data_present?(payload)
13
+
14
+ # global validation error event
15
+ self.fnord.event({
16
+ :_type => event_name,
17
+ :payload => payload
18
+ })
19
+ # class level validation error event
20
+ self.fnord.event({
21
+ :_type => "validation_rage_error.#{payload.keys.first.to_s.downcase}",
22
+ :payload => payload.values.first.keys
23
+ })
24
+ # two events are enough for now
25
+ ## attribute level validation error event
26
+ #payload.values.first.each do |attribute, error_messages|
27
+ # self.fnord.event({
28
+ # :_type => "validation_rage_error.#{payload.keys.first.to_s.downcase}.#{attribute}",
29
+ # :payload => error_messages
30
+ # })
31
+ #end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ module ValidationRage
2
+ class LogNotifier < BaseNotifier
3
+ attr_accessor :logger, :log_level
4
+ def initialize(args)
5
+ self.logger = args[:logger]
6
+ self.log_level = args[:log_level] || :warn
7
+ end
8
+ # TODO: optimize log format
9
+ def call(event_name, payload)
10
+ return if payload.values.first && payload.values.first.empty?
11
+ self.logger.send(self.log_level, "#{event_name} #{payload}")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module ValidationRage
2
+ module ModelExtension
3
+
4
+ def self.included(base)
5
+ base.after_validation(:notify_validation_rage)
6
+ end
7
+
8
+ def notify_validation_rage(context = {})
9
+ ActiveSupport::Notifications.publish("validation_rage:errors", {self.class.name => self.errors.to_hash, :context => context})
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module ValidationRage
2
+ class Engine < ::Rails::Engine
3
+
4
+ config.validation_rage = ActiveSupport::OrderedOptions.new
5
+
6
+ config.validation_rage.attach_to = []
7
+ config.validation_rage.attach_to = [ActiveRecord::Base] if defined?(ActiveRecord::Base) # really?!
8
+
9
+ config.validation_rage.notifier = {"Log" => {:log_level => :warn, :logger => nil}} # defaults to log everything to the logger
10
+
11
+ initializer "validation_rage.configure_subscribers" do |app|
12
+ # TODO move this into a testable setup method and refactor! -
13
+
14
+ if app.config.validation_rage # if validation rage is enabled
15
+ app.config.validation_rage.notifier.each do |name, options|
16
+ options[:logger] ||= Rails.logger
17
+ ValidationRage.const_get("#{name}Notifier").new(options).subscribe! # initialize the notifier and subscribe to the active suppot notification messages
18
+ end
19
+
20
+ # attach validation rage integration extensions to controllers (with after filter) or models (with after_validation filters)
21
+ controllers = {}
22
+ app.config.validation_rage.attach_to.each do |klass_or_action|
23
+ # if its a string we collect the controller and actions to later add a after_filter
24
+ if klass_or_action.is_a?(String)
25
+ controller, action = klass_or_action.split("#")
26
+ controllers[controller] ||= []
27
+ controllers[controller] << action
28
+ else
29
+ klass.send(:include, ValidationRage::ModelExtension)
30
+ end
31
+ end
32
+ # now addingt the after filter to controllers
33
+ controllers.each do |controller, actions|
34
+ controller = "Application" if controller == "*"
35
+ controller_class = "#{controller}Controller".classify.constantize rescue next # just ignore errors for now
36
+ controller_class.send(:include, ValidationRage::ControllerExtension)
37
+ options = {:only => actions} unless actions.any? {|a| a == "*"}
38
+ controller_class.send(:after_filter, :notify_validation_rage, options)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ require "socket"
2
+ module ValidationRage
3
+ class UdpNotifier < BaseNotifier
4
+
5
+ attr_accessor :socket, :host, :port
6
+ def initialize(args)
7
+ self.socket = UDPSocket.new
8
+ self.host = args[:host]
9
+ self.port = args[:port]
10
+ end
11
+
12
+ def call(event_name, payload)
13
+ self.socket.send(payload.to_json, 0, self.host, self.port)
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,19 @@
1
+ require "socket"
2
+ module ValidationRage
3
+ class ValidationRageNotifier < BaseNotifier
4
+
5
+ attr_accessor :socket, :host, :port, :api_key
6
+ def initialize(args)
7
+ self.socket = UDPSocket.new
8
+ self.host = "localhost"
9
+ self.port = 33333
10
+ self.api_key = args[:api_key]
11
+ end
12
+
13
+ def call(event_name, payload)
14
+ data = {:api_key => self.api_key, :payload => payload}
15
+ self.socket.send(data.to_json, 0, self.host, self.port)
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,3 @@
1
+ module ValidationRage
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,13 @@
1
+ require "validation_rage/version"
2
+ require "active_support/notifications"
3
+ require 'validation_rage/rails' if defined?(Rails)
4
+ require "json"
5
+ module ValidationRage
6
+ autoload :BaseNotifier, "validation_rage/base_notifier"
7
+ autoload :LogNotifier, "validation_rage/log_notifier"
8
+ autoload :UdpNotifier, "validation_rage/udp_notifier"
9
+ autoload :ValidationRageNotifier, "validation_rage/validation_rage_notifier"
10
+ autoload :FnordMetricNotifier, "validation_rage/fnord_metric_notifier"
11
+ autoload :ModelExtension, "validation_rage/model_extension"
12
+ autoload :ControllerExtension, "validation_rage/controller_extension"
13
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'bundler/setup'
3
+ Bundler.require
4
+ require 'minitest/autorun'
5
+ require 'minitest/mock'
6
+ require "mocha"
7
+
8
+ require 'validation_rage'
9
+
10
+
11
+ class MockModel
12
+ def self.after_validation(method)
13
+ @@after_validation_method = method
14
+ end
15
+ def self.after_validation_method
16
+ @@after_validation_method
17
+ end
18
+ def self.name
19
+ "MockClass"
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+
3
+ class ValidationRage::BaseNotifierTest < MiniTest::Unit::TestCase
4
+
5
+ def test_call_raises_a_not_implemented_error
6
+ assert_raises(ValidationRage::BaseNotifier::NotImplementedError) { ValidationRage::BaseNotifier.new.call("event", {}) }
7
+ end
8
+
9
+ def test_subscribe_to_validation_rage_events
10
+ notifier = ValidationRage::BaseNotifier.new
11
+ ActiveSupport::Notifications.expects(:subscribe).with(/validation_rage:.*/, notifier)
12
+ notifier.subscribe!
13
+ end
14
+
15
+ def test_data_present
16
+ notifier = ValidationRage::BaseNotifier.new
17
+ assert notifier.data_present?("klass" => {:error => :message})
18
+ assert !notifier.data_present?("klass" => {})
19
+ assert !notifier.data_present?({})
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ require "test_helper"
2
+
3
+ # TODO: guess this test could be written nicer
4
+ class MockController
5
+ def request
6
+ OpenStruct.new(:filtered_parameters => ["filtered", "params"], :path => "/mock/path", :uuid => "uuid", :referrer => "referrer")
7
+ end
8
+ def params
9
+ {:controller => "Mock", :action => "create"}
10
+ end
11
+ def initialize(records={})
12
+ records.each do |name,record|
13
+ instance_variable_set("@#{name}", record)
14
+ end
15
+ end
16
+ end
17
+ class ValidationRage::ControllerExtensionTest < MiniTest::Unit::TestCase
18
+
19
+ def setup
20
+ @klass = Class.new(MockController)
21
+ @klass.send(:include, ValidationRage::ControllerExtension)
22
+ end
23
+
24
+ def test_errors_present
25
+ assert !@klass.new(:var => "not responding to errors").validation_rage_errors_present?(:"@var")
26
+ assert !@klass.new(:var => mock(:errors => [])).validation_rage_errors_present?(:"@var")
27
+ assert !@klass.new(:_var => "internal only vars should be ignored").validation_rage_errors_present?(:"@_var")
28
+ assert @klass.new(:var => mock(:errors => ["not empty"])).validation_rage_errors_present?(:"@var")
29
+ end
30
+
31
+ def test_validation_rage_context
32
+ assert_equal({
33
+ :controller => "Mock",
34
+ :action => "create",
35
+ :request_path => "/mock/path",
36
+ :params => ["filtered","params"],
37
+ :request_uuid => "uuid",
38
+ :referrer => "referrer"
39
+ }, @klass.new.validation_rage_context)
40
+ end
41
+
42
+ def test_notify_validation_rage
43
+ record = MockModel.new
44
+ record.expects(:errors).at_least_once.returns(mock(:"empty?"=>false, :to_hash=>{:errors => :hash}))
45
+ controller_instance = @klass.new(:record => record, :foo => :bar)
46
+ ActiveSupport::Notifications.expects(:publish).with("validation_rage:errors", {
47
+ "MockClass" => {:errors => :hash},
48
+ :context => {
49
+ :controller => "Mock",
50
+ :action => "create",
51
+ :request_path => "/mock/path",
52
+ :params => ["filtered","params"],
53
+ :request_uuid => "uuid",
54
+ :referrer => "referrer"
55
+ }
56
+ })
57
+ controller_instance.notify_validation_rage
58
+ end
59
+
60
+ end
@@ -0,0 +1,23 @@
1
+ require "test_helper"
2
+
3
+ class ValidationRage::LogNotifierTest < MiniTest::Unit::TestCase
4
+
5
+
6
+ def test_defaul_log_level
7
+ assert_equal :warn, ValidationRage::LogNotifier.new({}).log_level
8
+ end
9
+ def test_log_validation_errors_to_logger
10
+ payload = {"Class" => {:name => ["missing"]}}
11
+ mocked_logger = mock()
12
+ mocked_logger.expects(:info).with("event_name #{payload.inspect}")
13
+ log_notifier = ValidationRage::LogNotifier.new(:logger => mocked_logger, :log_level => :info)
14
+ log_notifier.call("event_name", payload)
15
+ end
16
+
17
+ def test_ignore_empty_errors
18
+ mocked_logger = mock()
19
+ mocked_logger.expects(:warn).never
20
+ notifier = ValidationRage::LogNotifier.new(:logger => mocked_logger)
21
+ notifier.call("event_name", "Class" => {})
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+
3
+ # TODO: guess this test could be written nicer
4
+ class ValidationRage::ModelExtensionTest < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @klass = Class.new(MockModel)
8
+ @klass.send(:include, ValidationRage::ModelExtension)
9
+ end
10
+
11
+ def test_register_after_validation_callback
12
+ assert_equal :notify_validation_rage, @klass.after_validation_method
13
+ assert @klass.new.respond_to?(:notify_validation_rage)
14
+ end
15
+
16
+ def test_publish_active_support_notification
17
+ instance = @klass.new
18
+ instance.expects(:errors).returns(mock(:to_hash=>{:errors => :hash}))
19
+ ActiveSupport::Notifications.expects(:publish).with("validation_rage:errors", {"MockClass" => {:errors => :hash}, :context => {}})
20
+ instance.notify_validation_rage
21
+ end
22
+
23
+ def test_passing_a_context_hash_to_notification_call
24
+ instance = @klass.new
25
+ instance.expects(:errors).returns(mock(:to_hash=>{:errors => :hash}))
26
+ ActiveSupport::Notifications.expects(:publish).with("validation_rage:errors", {"MockClass" => {:errors => :hash}, :context => {:controller => "Users", :action => "edit"}})
27
+ instance.notify_validation_rage({:controller => "Users", :action => "edit"})
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+ require "socket"
3
+ class ValidationRage::UdpNotifierTest < MiniTest::Unit::TestCase
4
+
5
+ def test_connect_to_socket
6
+ UDPSocket.expects(:new).returns("socket")
7
+ notifier = ValidationRage::UdpNotifier.new(:host => "localhost", :port => 33333)
8
+ assert_equal "socket", notifier.socket
9
+ assert_equal "localhost", notifier.host
10
+ assert_equal 33333, notifier.port
11
+ end
12
+
13
+ def test_send_json_encoded_payload_through_socket
14
+ notifier = ValidationRage::UdpNotifier.new(:host => "host", :port => "port")
15
+ payload = {"Class" => {:error => :message}}
16
+ socket = mock()
17
+ socket.expects(:send).with(payload.to_json,0, "host", "port")
18
+ notifier.expects(:socket).returns(socket)
19
+ notifier.call("event_name", payload)
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/validation_rage/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Michael Bumann"]
6
+ gem.email = ["michael@railslove.com"]
7
+ gem.description = %q{log active model validation errors to get better insights in the usability of your forms}
8
+ gem.summary = %q{log active model validation errors to get better insights in the usability of your forms}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "validation_rage"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ValidationRage::VERSION
17
+ gem.add_development_dependency 'rake'
18
+ gem.add_development_dependency 'mocha'
19
+ gem.add_dependency 'activesupport'
20
+ gem.add_dependency 'json'
21
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: validation_rage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Bumann
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &2156416240 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2156416240
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ requirement: &2156459260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2156459260
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &2156470080 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2156470080
47
+ - !ruby/object:Gem::Dependency
48
+ name: json
49
+ requirement: &2156465740 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *2156465740
58
+ description: log active model validation errors to get better insights in the usability
59
+ of your forms
60
+ email:
61
+ - michael@railslove.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - .travis.yml
68
+ - Gemfile
69
+ - LICENSE
70
+ - README.md
71
+ - Rakefile
72
+ - lib/validation_rage.rb
73
+ - lib/validation_rage/base_notifier.rb
74
+ - lib/validation_rage/controller_extension.rb
75
+ - lib/validation_rage/fnord_metric_notifier.rb
76
+ - lib/validation_rage/log_notifier.rb
77
+ - lib/validation_rage/model_extension.rb
78
+ - lib/validation_rage/rails.rb
79
+ - lib/validation_rage/udp_notifier.rb
80
+ - lib/validation_rage/validation_rage_notifier.rb
81
+ - lib/validation_rage/version.rb
82
+ - test/test_helper.rb
83
+ - test/validation_rage/base_notifier_test.rb
84
+ - test/validation_rage/controller_extension_test.rb
85
+ - test/validation_rage/log_notifier_test.rb
86
+ - test/validation_rage/model_extension_test.rb
87
+ - test/validation_rage/udp_notifier_test.rb
88
+ - validation_rage.gemspec
89
+ homepage: ''
90
+ licenses: []
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 1.8.10
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: log active model validation errors to get better insights in the usability
113
+ of your forms
114
+ test_files:
115
+ - test/test_helper.rb
116
+ - test/validation_rage/base_notifier_test.rb
117
+ - test/validation_rage/controller_extension_test.rb
118
+ - test/validation_rage/log_notifier_test.rb
119
+ - test/validation_rage/model_extension_test.rb
120
+ - test/validation_rage/udp_notifier_test.rb
121
+ has_rdoc: