whyvalidationssuckin96 1.0.0
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.
- 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
|