whyvalidationssuckin96 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.md +121 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/doc/ActiveRecord/RecordInvalid.html +258 -0
- data/doc/ActiveRecord.html +93 -0
- data/doc/FalseClass.html +87 -0
- data/doc/NilClass.html +87 -0
- data/doc/Numeric.html +87 -0
- data/doc/Object.html +79 -0
- data/doc/String.html +87 -0
- data/doc/TrueClass.html +87 -0
- data/doc/WhyValidationsSuckIn96/ActiveRecord/InstanceMethods.html +156 -0
- data/doc/WhyValidationsSuckIn96/ActiveRecord.html +192 -0
- data/doc/WhyValidationsSuckIn96/AttributeBasedValidation.html +464 -0
- data/doc/WhyValidationsSuckIn96/SkippableValidation.html +194 -0
- data/doc/WhyValidationsSuckIn96/ValidatesAcceptance.html +254 -0
- data/doc/WhyValidationsSuckIn96/ValidatesAssociated.html +250 -0
- data/doc/WhyValidationsSuckIn96/ValidatesConfirmation.html +251 -0
- data/doc/WhyValidationsSuckIn96/ValidatesExclusion.html +388 -0
- data/doc/WhyValidationsSuckIn96/ValidatesFormat.html +387 -0
- data/doc/WhyValidationsSuckIn96/ValidatesInclusion.html +388 -0
- data/doc/WhyValidationsSuckIn96/ValidatesLength.html +469 -0
- data/doc/WhyValidationsSuckIn96/ValidatesNumericality.html +267 -0
- data/doc/WhyValidationsSuckIn96/ValidatesPresence.html +244 -0
- data/doc/WhyValidationsSuckIn96/ValidatesUniqueness.html +289 -0
- data/doc/WhyValidationsSuckIn96/Validation.html +934 -0
- data/doc/WhyValidationsSuckIn96/ValidationBuilder.html +391 -0
- data/doc/WhyValidationsSuckIn96/ValidationSupport/ClassMethods.html +249 -0
- data/doc/WhyValidationsSuckIn96/ValidationSupport/InstanceMethods.html +484 -0
- data/doc/WhyValidationsSuckIn96/ValidationSupport.html +168 -0
- data/doc/WhyValidationsSuckIn96.html +97 -0
- data/doc/_index.html +346 -0
- data/doc/class_list.html +293 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +23 -0
- data/doc/css/style.css +263 -0
- data/doc/file.README.html +173 -0
- data/doc/file_list.html +29 -0
- data/doc/index.html +173 -0
- data/doc/js/app.js +91 -0
- data/doc/js/full_list.js +39 -0
- data/doc/js/jquery.js +19 -0
- data/doc/method_list.html +462 -0
- data/doc/top-level-namespace.html +81 -0
- data/lib/whyvalidationssuckin96/attribute_based_validation.rb +46 -0
- data/lib/whyvalidationssuckin96/constants.rb +3 -0
- data/lib/whyvalidationssuckin96/ext/blank.rb +47 -0
- data/lib/whyvalidationssuckin96/macros/validates_acceptance.rb +36 -0
- data/lib/whyvalidationssuckin96/macros/validates_associated.rb +33 -0
- data/lib/whyvalidationssuckin96/macros/validates_confirmation.rb +40 -0
- data/lib/whyvalidationssuckin96/macros/validates_exclusion.rb +38 -0
- data/lib/whyvalidationssuckin96/macros/validates_format.rb +38 -0
- data/lib/whyvalidationssuckin96/macros/validates_inclusion.rb +38 -0
- data/lib/whyvalidationssuckin96/macros/validates_length.rb +98 -0
- data/lib/whyvalidationssuckin96/macros/validates_numericality.rb +56 -0
- data/lib/whyvalidationssuckin96/macros/validates_presence.rb +30 -0
- data/lib/whyvalidationssuckin96/macros.rb +9 -0
- data/lib/whyvalidationssuckin96/rails/active_record.rb +94 -0
- data/lib/whyvalidationssuckin96/rails/macros/validates_uniqueness.rb +87 -0
- data/lib/whyvalidationssuckin96/rails/macros.rb +1 -0
- data/lib/whyvalidationssuckin96/skippable_validation.rb +59 -0
- data/lib/whyvalidationssuckin96/validation.rb +88 -0
- data/lib/whyvalidationssuckin96/validation_builder.rb +56 -0
- data/lib/whyvalidationssuckin96/validation_support.rb +74 -0
- data/lib/whyvalidationssuckin96.rb +4 -0
- data/test/attribute_based_validation_test.rb +58 -0
- data/test/macros/validates_acceptance_test.rb +64 -0
- data/test/macros/validates_associated_test.rb +60 -0
- data/test/macros/validates_confirmation_test.rb +63 -0
- data/test/macros/validates_exclusion_test.rb +37 -0
- data/test/macros/validates_format_test.rb +43 -0
- data/test/macros/validates_inclusion_test.rb +37 -0
- data/test/macros/validates_length_test.rb +179 -0
- data/test/macros/validates_numericality_test.rb +129 -0
- data/test/macros/validates_presence_test.rb +31 -0
- data/test/rails/active_record_test.rb +187 -0
- data/test/rails/active_record_test_helper.rb +90 -0
- data/test/rails/macros/validates_uniqueness_test.rb +153 -0
- data/test/skippable_validation_test.rb +102 -0
- data/test/teststrap.rb +4 -0
- data/test/validation_builder_test.rb +62 -0
- data/test/validation_support_test.rb +209 -0
- data/test/validation_test.rb +101 -0
- data/whyvalidationssuckin96.gemspec +153 -0
- metadata +189 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'whyvalidationssuckin96'
|
2
|
+
require 'active_record'
|
3
|
+
require 'active_record/base'
|
4
|
+
require 'active_record/callbacks'
|
5
|
+
require 'whyvalidationssuckin96/rails/macros'
|
6
|
+
|
7
|
+
module WhyValidationsSuckIn96
|
8
|
+
|
9
|
+
module ActiveRecord
|
10
|
+
RemovableInstanceMethods = %w[invalid? validate_on_create validate_on_update validate errors]
|
11
|
+
RemovableClassMethods = %w[validate validate_on_create validate_on_update validates_format_of validates_each
|
12
|
+
validates_inclusion_of validates_size_of validates_confirmation_of validates_exclusion_of
|
13
|
+
validates_uniqueness_of validates_associated validates_acceptance_of
|
14
|
+
validates_numericality_of validates_presence_of validates_length_of]
|
15
|
+
|
16
|
+
def self.included(klass_or_mod)
|
17
|
+
remove_active_record_validation_related_methods_from(klass_or_mod)
|
18
|
+
klass_or_mod.instance_eval do
|
19
|
+
include WhyValidationsSuckIn96::ValidationSupport
|
20
|
+
include WhyValidationsSuckIn96::ActiveRecord::InstanceMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# FIXME - holy mother of god is this a nasty method
|
27
|
+
def self.remove_active_record_validation_related_methods_from(klass_or_mod)
|
28
|
+
method_map = {klass_or_mod => RemovableInstanceMethods, (class<<klass_or_mod;self;end) => RemovableClassMethods}
|
29
|
+
method_map.each do |context, removable_methods|
|
30
|
+
context.instance_eval do
|
31
|
+
removable_methods.each do |removable_method|
|
32
|
+
begin
|
33
|
+
remove_method removable_method
|
34
|
+
rescue => e
|
35
|
+
undef_method removable_method
|
36
|
+
end
|
37
|
+
end # removable_methods.each
|
38
|
+
end # context.instance_eval
|
39
|
+
end # method_map.each
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
|
44
|
+
def self.included(klass_or_mod)
|
45
|
+
klass_or_mod.module_eval do
|
46
|
+
alias_method :valid_without_callbacks?, :valid_with_lifecycle_checking?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validations_for_current_lifecycle
|
53
|
+
validations_for_save + (new_record? ? validations_for_create : validations_for_update)
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid_with_lifecycle_checking?
|
57
|
+
validations_for_current_lifecycle.collect do |validation|
|
58
|
+
validation.validates?
|
59
|
+
end.all?
|
60
|
+
end
|
61
|
+
|
62
|
+
def validations_for_update
|
63
|
+
all_validations.select do |validation|
|
64
|
+
validation.options[:on] == :update
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validations_for_create
|
69
|
+
all_validations.select do |validation|
|
70
|
+
validation.options[:on] == :create
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validations_for_save
|
75
|
+
all_validations.select do |validation|
|
76
|
+
validation.options[:on].nil? || validation.options[:on] == :save
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end # InstanceMethods
|
81
|
+
end # ActiveRecord
|
82
|
+
end # WhyValidationsSuckIn96
|
83
|
+
|
84
|
+
module ActiveRecord
|
85
|
+
class RecordInvalid < ActiveRecordError
|
86
|
+
attr_reader :record
|
87
|
+
def initialize(record)
|
88
|
+
@record = record
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end # RecordInvalid
|
92
|
+
end # ActiveRecord
|
93
|
+
|
94
|
+
ActiveRecord::Base.instance_eval { include WhyValidationsSuckIn96::ActiveRecord }
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'whyvalidationssuckin96/skippable_validation'
|
2
|
+
require 'whyvalidationssuckin96/attribute_based_validation'
|
3
|
+
|
4
|
+
module WhyValidationsSuckIn96
|
5
|
+
|
6
|
+
# Checks to see if a given attribute is present in any other records in the databaser
|
7
|
+
#
|
8
|
+
# @example Default usage
|
9
|
+
# setup_validations do
|
10
|
+
# validates_uniqueness_of :name
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @example Check in a case sensitive fashion
|
14
|
+
# setup_validations do
|
15
|
+
# validates_uniqueness_of :name, :case_sensitive => true
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Scope uniqueness to another attribute
|
19
|
+
# setup_validations do
|
20
|
+
# validates_uniqueness_of :name, :scope => :account_id
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Scope uniqueness to multiple other attributes
|
24
|
+
# setup_validations do
|
25
|
+
# validates_uniqueness_of :name, :scope => [:account_id, :domain]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @example When used with STI, check only uniqueness of records of the current type
|
29
|
+
# setup_validations do
|
30
|
+
# validates_uniqueness_of :name, :base_class_scope => false
|
31
|
+
# end
|
32
|
+
class ValidatesUniqueness < Validation
|
33
|
+
DefaultOptions = {:message => "has already been taken", :case_sensitive => false, :base_class_scope => true}
|
34
|
+
|
35
|
+
include WhyValidationsSuckIn96::SkippableValidation
|
36
|
+
include WhyValidationsSuckIn96::AttributeBasedValidation
|
37
|
+
|
38
|
+
def validate
|
39
|
+
super
|
40
|
+
results = find_results
|
41
|
+
result_ids = results.collect { |r| r[scope_primary_key] }
|
42
|
+
if results.empty? || result_ids.include?(validatable[scope_primary_key])
|
43
|
+
pass
|
44
|
+
elsif !results.empty? && options[:case_sensitive]
|
45
|
+
(results.any? { |r| r[attribute].to_s == attribute_value.to_s }) ? fail : pass
|
46
|
+
else
|
47
|
+
fail
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def find_results
|
54
|
+
scope_class.send(:with_exclusive_scope) do
|
55
|
+
scope_class.send(:with_scope, :find => {:conditions => find_conditions}) do
|
56
|
+
scope_class.find(:all, :conditions => scope_conditions)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def scope_columns
|
62
|
+
Array(options[:scope])
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_conditions
|
66
|
+
["LOWER(#{scope_class.connection.quote_column_name(attribute.to_s)}) = LOWER(?)", attribute_value]
|
67
|
+
end
|
68
|
+
|
69
|
+
def scope_conditions
|
70
|
+
scope_columns.inject({}) do |conds,col|
|
71
|
+
conds[col] = validatable[col]
|
72
|
+
conds
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def scope_primary_key
|
77
|
+
scope_class.primary_key
|
78
|
+
end
|
79
|
+
|
80
|
+
def scope_class
|
81
|
+
options[:base_class_scope] ? validatable.class.base_class : validatable.class
|
82
|
+
end
|
83
|
+
|
84
|
+
end # Validation
|
85
|
+
|
86
|
+
ValidationBuilder.register_macro :validates_uniqueness_of, WhyValidationsSuckIn96::ValidatesUniqueness
|
87
|
+
end # WhyValidationsSuckIn96
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'whyvalidationssuckin96/rails/macros/validates_uniqueness'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module WhyValidationsSuckIn96
|
2
|
+
# A mixin to handle specifying :if and :unless options to check before performing validation.
|
3
|
+
#
|
4
|
+
# @example Validate if a given block returns true
|
5
|
+
# setup_validations do
|
6
|
+
# validates_associated :tracks, :if => lambda { !validatable.tracks.empty? }
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# @example Validate if a given method on the validatable object returns true
|
10
|
+
# setup_validations do
|
11
|
+
# validates_associated :tracks, :if => :allow_validation
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def allow_validation
|
15
|
+
# false
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Validate unless a given block returns true
|
19
|
+
# setup_validations do
|
20
|
+
# validates_associated :tracks, :unless => lambda { validatable.tracks.empty? }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Validate unless a given method on the validatable object returns true
|
24
|
+
# setup_validations do
|
25
|
+
# validates_associated :tracks, :unless => :disallow_validation
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def disallow_validation
|
29
|
+
# true
|
30
|
+
# end
|
31
|
+
module SkippableValidation
|
32
|
+
|
33
|
+
def validate
|
34
|
+
skip if skip_validation?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def skip
|
40
|
+
throw :validation_done, nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def skip_validation?
|
44
|
+
skip_if? || skip_unless?
|
45
|
+
end
|
46
|
+
|
47
|
+
def skip_unless?
|
48
|
+
return false unless options.has_key?(:unless)
|
49
|
+
check = options[:unless].is_a?(Proc) ? instance_eval(&options[:unless]) : validatable.send(options[:unless])
|
50
|
+
check == true
|
51
|
+
end
|
52
|
+
|
53
|
+
def skip_if?
|
54
|
+
return false unless options.has_key?(:if)
|
55
|
+
check = options[:if].is_a?(Proc) ? instance_eval(&options[:if]) : validatable.send(options[:if])
|
56
|
+
check == false
|
57
|
+
end
|
58
|
+
end # SkippableValidation
|
59
|
+
end # WhyValidationsSuckIn96
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module WhyValidationsSuckIn96
|
2
|
+
|
3
|
+
# Base class to use when implementing validations.
|
4
|
+
class Validation
|
5
|
+
|
6
|
+
# A hash of default options for the validation to use.
|
7
|
+
DefaultOptions = {}
|
8
|
+
|
9
|
+
# The options the validation was initialized with
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
# The object the validation is validating
|
13
|
+
attr_reader :validatable
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :name
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Object] validatable An object to be validated
|
20
|
+
# @param [Hash] options The options to set up the validation with
|
21
|
+
def initialize(validatable, options = {})
|
22
|
+
@validatable = validatable
|
23
|
+
@options = self.class::DefaultOptions.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates a new subclass of this class, used when defining custom validations with a block
|
27
|
+
def self.new_subclass(name, def_block)
|
28
|
+
Class.new(self) do
|
29
|
+
self.name = name.to_sym
|
30
|
+
define_method(:validate, &def_block)
|
31
|
+
private :validate
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<WhyValidationsSuckIn96::Validation subclass for validating '#{self.class.name}'> #{super}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Has this validation passed?
|
40
|
+
# @return [true, false]
|
41
|
+
def passed?
|
42
|
+
@passed == true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Has this validation failed?
|
46
|
+
# @return [true, false]
|
47
|
+
def failed?
|
48
|
+
@passed == false
|
49
|
+
end
|
50
|
+
|
51
|
+
# Has this validation run?
|
52
|
+
# @return [true, false]
|
53
|
+
def has_run?
|
54
|
+
@passed != nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Performs the validation, returning true or false if the validation passes or fails,
|
58
|
+
# or nil if the validation will not run.
|
59
|
+
# @return [true, false, nil]
|
60
|
+
def validates?
|
61
|
+
reset
|
62
|
+
@passed = catch :validation_done do
|
63
|
+
validate
|
64
|
+
pass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# The failure message for this validation.
|
69
|
+
def message
|
70
|
+
@options[:message] || "failed validation"
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def reset
|
76
|
+
@passed = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def pass
|
80
|
+
throw :validation_done, true
|
81
|
+
end
|
82
|
+
|
83
|
+
def fail
|
84
|
+
throw :validation_done, false
|
85
|
+
end
|
86
|
+
|
87
|
+
end # Validation
|
88
|
+
end # WhyValidationsSuckIn96
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'whyvalidationssuckin96/validation'
|
2
|
+
|
3
|
+
module WhyValidationsSuckIn96
|
4
|
+
class ValidationBuilder
|
5
|
+
|
6
|
+
# @param [Module, Class] klass_or_mod The Class or Module to add validations to
|
7
|
+
# @param [Proc] definition_block The block to evaluate to define validations
|
8
|
+
def initialize(klass_or_mod, definition_block)
|
9
|
+
@klass_or_mod = klass_or_mod
|
10
|
+
@definition_block = definition_block
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_validations!
|
14
|
+
instance_eval(&@definition_block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Registers a macro to be used in setup_validations blocks
|
18
|
+
# @param [Symbol] macro_name The name to register this macro with
|
19
|
+
# @param [Class] validation_class The class implementing the validation
|
20
|
+
def self.register_macro(macro_name, validation_class)
|
21
|
+
define_method(macro_name) do |*args|
|
22
|
+
attrs, options = extract_options(args)
|
23
|
+
attrs.each do |attr|
|
24
|
+
add_validation(validation_class, options.merge(:attribute => attr))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def add_validation(klass, options)
|
32
|
+
@klass_or_mod.validation_collection << [klass, options]
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate(validation_name, options = {}, &def_block)
|
36
|
+
existing = @klass_or_mod.validation_collection.detect do |validation, opts|
|
37
|
+
validation.name == validation_name
|
38
|
+
end
|
39
|
+
|
40
|
+
if existing
|
41
|
+
@klass_or_mod.validation_collection.delete(existing)
|
42
|
+
end
|
43
|
+
|
44
|
+
add_validation(new_validation(validation_name, def_block), options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract_options(args_array)
|
48
|
+
args, opts = args_array.last.is_a?(Hash) ? [args_array[0..-2], args_array.last] : [args_array, {}]
|
49
|
+
end
|
50
|
+
|
51
|
+
def new_validation(validation_name, def_block)
|
52
|
+
WhyValidationsSuckIn96::Validation.new_subclass(validation_name, def_block)
|
53
|
+
end
|
54
|
+
|
55
|
+
end # ValidationBuilder
|
56
|
+
end # WhyValidationsSuckIn96
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'whyvalidationssuckin96/validation_builder'
|
2
|
+
|
3
|
+
module WhyValidationsSuckIn96
|
4
|
+
module ValidationSupport
|
5
|
+
|
6
|
+
def self.included(klass_or_mod)
|
7
|
+
klass_or_mod.module_eval do
|
8
|
+
extend WhyValidationsSuckIn96::ValidationSupport::ClassMethods
|
9
|
+
include WhyValidationsSuckIn96::ValidationSupport::InstanceMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Instance methods added to any class or module that mixes in ValidationSupport
|
14
|
+
module InstanceMethods
|
15
|
+
|
16
|
+
# Is this object invalid?
|
17
|
+
# @return [true, false]
|
18
|
+
def invalid?
|
19
|
+
!valid?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Is this object valid?
|
23
|
+
# @return [true, false]
|
24
|
+
def valid?
|
25
|
+
all_validations.collect do |validation|
|
26
|
+
validation.validates?
|
27
|
+
end.all?
|
28
|
+
end
|
29
|
+
|
30
|
+
# An array of instances of failed validations
|
31
|
+
# @return [Array]
|
32
|
+
def failed_validations
|
33
|
+
all_validations.select { |validation| validation.failed? }
|
34
|
+
end
|
35
|
+
|
36
|
+
# An array of instances of passed validations
|
37
|
+
# @return [Array]
|
38
|
+
def passed_validations
|
39
|
+
all_validations.select { |validation| validation.passed? }
|
40
|
+
end
|
41
|
+
|
42
|
+
# An array of instances of all validations for this object
|
43
|
+
# @return [Array]
|
44
|
+
def all_validations
|
45
|
+
@all_validations ||= self.class.validation_collection.collect do |(vc,opts)|
|
46
|
+
vc.new(self, opts)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end # InstanceMethods
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
|
54
|
+
# An array of arrays, the first element of each being the validation subclass that will be instantiated
|
55
|
+
# when validation is performed, the last element being the options the validation will be instantiated
|
56
|
+
# with.
|
57
|
+
# @return [Array]
|
58
|
+
def validation_collection
|
59
|
+
@validation_collection ||= begin
|
60
|
+
ancestor_with_validations = ancestors[1..-1].detect{|anc| anc.respond_to?(:validation_collection) }
|
61
|
+
ancestor_with_validations ? ancestor_with_validations.validation_collection.dup : []
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets up validations for the class or module this module has been mixed into
|
66
|
+
def setup_validations(&definition_block)
|
67
|
+
self.validation_collection ||= ancestors.detect{|anc| !anc.validation_collection.nil?}.validation_collection.dup
|
68
|
+
builder = ValidationBuilder.new(self, definition_block)
|
69
|
+
builder.create_validations!
|
70
|
+
end
|
71
|
+
|
72
|
+
end # ClassMethods
|
73
|
+
end # ValidationSupport
|
74
|
+
end # WhyValidationsSuckIn96
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'whyvalidationssuckin96/attribute_based_validation'
|
3
|
+
|
4
|
+
context "attribute based validation mixin" do
|
5
|
+
context "when mixed into a class" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
Class.new(WhyValidationsSuckIn96::Validation) do
|
9
|
+
include WhyValidationsSuckIn96::SkippableValidation
|
10
|
+
include WhyValidationsSuckIn96::AttributeBasedValidation
|
11
|
+
|
12
|
+
def validate
|
13
|
+
super
|
14
|
+
pass
|
15
|
+
end
|
16
|
+
|
17
|
+
end # Class.new
|
18
|
+
end # setup
|
19
|
+
|
20
|
+
should "fail if no attribute is specified during construction" do
|
21
|
+
topic.new(Object.new)
|
22
|
+
end.raises(ArgumentError, "The attribute to validate must be specified as :attribute")
|
23
|
+
|
24
|
+
should "add an attribute accessor" do
|
25
|
+
topic.new(Object.new, :attribute => :foo).attribute
|
26
|
+
end.equals(:foo)
|
27
|
+
|
28
|
+
context "when using :allow_nil" do
|
29
|
+
|
30
|
+
should "skip validation if the validatable object is #nil?" do
|
31
|
+
inst = topic.new(OpenStruct.new(:test => nil), :allow_nil => true, :attribute => :test)
|
32
|
+
inst.validates?
|
33
|
+
inst.has_run?
|
34
|
+
end.equals(false)
|
35
|
+
|
36
|
+
should "not skip validation of the validatable object is non-#nil?" do
|
37
|
+
inst = topic.new(OpenStruct.new(:test => Object.new), :allow_nil => true, :attribute => :test)
|
38
|
+
inst.validates?
|
39
|
+
inst.has_run?
|
40
|
+
end
|
41
|
+
end # when using :allow_nil
|
42
|
+
|
43
|
+
context "when using :allow_blank" do
|
44
|
+
should "skip validation if the validatable object is #blank?" do
|
45
|
+
inst = topic.new(OpenStruct.new(:test => ""), :allow_blank => true, :attribute => :test)
|
46
|
+
inst.validates?
|
47
|
+
inst.has_run?
|
48
|
+
end.equals(false)
|
49
|
+
|
50
|
+
should "not skip validation if the validatable object is non-#blank?" do
|
51
|
+
inst = topic.new(OpenStruct.new(:test => "bzzt"), :allow_blank => true, :attribute => :test)
|
52
|
+
inst.validates?
|
53
|
+
inst.has_run?
|
54
|
+
end
|
55
|
+
end # when using :allow_blank
|
56
|
+
|
57
|
+
end # when mixed into a class
|
58
|
+
end # skippable validation mixin
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
|
3
|
+
context "validates acceptance" do
|
4
|
+
|
5
|
+
should "add a validation macro" do
|
6
|
+
WhyValidationsSuckIn96::ValidationBuilder.instance_methods
|
7
|
+
end.includes('validates_acceptance_of')
|
8
|
+
|
9
|
+
context "with some default options" do
|
10
|
+
setup do
|
11
|
+
WhyValidationsSuckIn96::ValidatesAcceptance.new(Object.new, :attribute => :accepted)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "have a message accessor with a default message" do
|
15
|
+
topic.message
|
16
|
+
end.equals("must be accepted")
|
17
|
+
|
18
|
+
should "default the :allow_nil option to true" do
|
19
|
+
topic.options[:allow_nil]
|
20
|
+
end
|
21
|
+
|
22
|
+
should "default the :accept option to allow the string '1'" do
|
23
|
+
topic.options[:accept]
|
24
|
+
end.equals("1")
|
25
|
+
end # with some default options
|
26
|
+
|
27
|
+
context "validating an object" do
|
28
|
+
validatable = OpenStruct.new(:accepted => false)
|
29
|
+
|
30
|
+
setup do
|
31
|
+
WhyValidationsSuckIn96::ValidatesAcceptance.new(validatable, :attribute => :accepted)
|
32
|
+
end
|
33
|
+
|
34
|
+
should "fail if the return value of the given attribute doesnt match the :accept option" do
|
35
|
+
validatable.accepted = false
|
36
|
+
topic.validates?
|
37
|
+
end.equals(false)
|
38
|
+
|
39
|
+
should "pass if the return value of the given attribute matches the :accept option" do
|
40
|
+
validatable.accepted = topic.options[:accept]
|
41
|
+
topic.validates?
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with some non-default options" do
|
45
|
+
setup do
|
46
|
+
WhyValidationsSuckIn96::ValidatesAcceptance.new(validatable, :attribute => :accepted, :accept => :what, :message => "bleh")
|
47
|
+
end
|
48
|
+
|
49
|
+
should "have a different message" do
|
50
|
+
topic.message
|
51
|
+
end.equals("bleh")
|
52
|
+
|
53
|
+
should "fail if the return value of the given attribute doesnt match the :accept option" do
|
54
|
+
validatable.accepted = false
|
55
|
+
topic.validates?
|
56
|
+
end.equals(false)
|
57
|
+
|
58
|
+
should "pass if the return value of the given attribute matches the :accept option" do
|
59
|
+
validatable.accepted = :what
|
60
|
+
topic.validates?
|
61
|
+
end
|
62
|
+
end # with some non-default options
|
63
|
+
end # validating an object
|
64
|
+
end # validates acceptance
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
|
3
|
+
context "validates associated" do
|
4
|
+
|
5
|
+
should "add a validation macro" do
|
6
|
+
WhyValidationsSuckIn96::ValidationBuilder.instance_methods
|
7
|
+
end.includes('validates_associated')
|
8
|
+
|
9
|
+
context "with some default options" do
|
10
|
+
setup do
|
11
|
+
WhyValidationsSuckIn96::ValidatesAssociated.new(Object.new, :attribute => :things)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "have a message accessor with a default message" do
|
15
|
+
topic.message
|
16
|
+
end.equals("is invalid")
|
17
|
+
end # with some default options
|
18
|
+
|
19
|
+
context "validating a singular association" do
|
20
|
+
associated = OpenStruct.new(:valid? => true)
|
21
|
+
validatable = OpenStruct.new(:thing => associated)
|
22
|
+
|
23
|
+
setup do
|
24
|
+
WhyValidationsSuckIn96::ValidatesAssociated.new(validatable, :attribute => :thing)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "be valid if associated object is valid" do
|
28
|
+
def associated.valid?; true; end
|
29
|
+
topic.validates?
|
30
|
+
end
|
31
|
+
|
32
|
+
should "be invalid if associated object is invalid" do
|
33
|
+
def associated.valid?; false; end
|
34
|
+
topic.validates?
|
35
|
+
end.equals(false)
|
36
|
+
end # validating a singular association
|
37
|
+
|
38
|
+
context "validating a collection association" do
|
39
|
+
associated = [OpenStruct.new(:valid? => true), OpenStruct.new(:valid? => false)]
|
40
|
+
validatable = OpenStruct.new(:things => associated)
|
41
|
+
|
42
|
+
setup do
|
43
|
+
WhyValidationsSuckIn96::ValidatesAssociated.new(validatable, :attribute => :things)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "be valid if all associated objects are valid" do
|
47
|
+
associated.each do |assoc|
|
48
|
+
def assoc.valid?; true; end
|
49
|
+
end
|
50
|
+
topic.validates?
|
51
|
+
end
|
52
|
+
|
53
|
+
should "be invalid if any associated objects are invalid" do
|
54
|
+
associated.each do |assoc|
|
55
|
+
def assoc.valid?; false; end
|
56
|
+
end
|
57
|
+
topic.validates?
|
58
|
+
end.equals(false)
|
59
|
+
end # validating a collection association
|
60
|
+
end # validates associated
|