usecasing_validations 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +22 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/lib/usecasing/depends_all.rb +21 -0
- data/lib/usecasing/validator.rb +31 -0
- data/lib/usecasing_validations/custom_validator.rb +21 -0
- data/lib/usecasing_validations/each_validator.rb +32 -0
- data/lib/usecasing_validations/errors.rb +364 -0
- data/lib/usecasing_validations/helpers.rb +50 -0
- data/lib/usecasing_validations/target.rb +44 -0
- data/lib/usecasing_validations/validations/format.rb +116 -0
- data/lib/usecasing_validations/validations/helper_methods.rb +16 -0
- data/lib/usecasing_validations/validations/length.rb +126 -0
- data/lib/usecasing_validations/validations/presence.rb +17 -0
- data/lib/usecasing_validations/validations/uniqueness.rb +56 -0
- data/lib/usecasing_validations/validator.rb +20 -0
- data/lib/usecasing_validations/version.rb +3 -0
- data/lib/usecasing_validations.rb +122 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/models/ruby_post.rb +9 -0
- data/spec/support/models/ruby_post_with_comments.rb +28 -0
- data/spec/support/usecases/validate_comments.rb +34 -0
- data/spec/support/usecases/validate_comments_custom_target.rb +14 -0
- data/spec/support/usecases/validate_depends_all.rb +24 -0
- data/spec/support/usecases/validate_post.rb +18 -0
- data/spec/support/usecases/validate_post_clear_errors.rb +9 -0
- data/spec/support/usecases/validate_uniq_comments.rb +77 -0
- data/spec/usecasing/validate_comments_custom_target_spec.rb +36 -0
- data/spec/usecasing/validate_comments_spec.rb +77 -0
- data/spec/usecasing/validate_depends_all_spec.rb +22 -0
- data/spec/usecasing/validate_post_clear_errors_spec.rb +48 -0
- data/spec/usecasing/validates_uniqueness_of_spec.rb +124 -0
- data/usecasing_validations.gemspec +29 -0
- metadata +166 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
class FormatValidator < EachValidator # :nodoc:
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
if options[:with]
|
7
|
+
regexp = option_call(record, :with)
|
8
|
+
record_error(record, attribute, :with, value) if value.to_s !~ regexp
|
9
|
+
elsif options[:without]
|
10
|
+
regexp = option_call(record, :without)
|
11
|
+
record_error(record, attribute, :without, value) if value.to_s =~ regexp
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_validity!
|
16
|
+
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
|
17
|
+
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
18
|
+
end
|
19
|
+
|
20
|
+
check_options_validity(options, :with)
|
21
|
+
check_options_validity(options, :without)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def option_call(record, name)
|
27
|
+
option = options[name]
|
28
|
+
option.respond_to?(:call) ? option.call(record) : option
|
29
|
+
end
|
30
|
+
|
31
|
+
def record_error(record, attribute, name, value)
|
32
|
+
record.errors.add(attribute, :invalid, Helpers._except(options, name).merge!(value: value))
|
33
|
+
end
|
34
|
+
|
35
|
+
def regexp_using_multiline_anchors?(regexp)
|
36
|
+
regexp.source.start_with?("^") ||
|
37
|
+
(regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_options_validity(options, name)
|
41
|
+
option = options[name]
|
42
|
+
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
|
43
|
+
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
|
44
|
+
elsif option && option.is_a?(Regexp) &&
|
45
|
+
regexp_using_multiline_anchors?(option) && options[:multiline] != true
|
46
|
+
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
|
47
|
+
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
|
48
|
+
":multiline => true option?"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module HelperMethods
|
54
|
+
# Validates whether the value of the specified attribute is of the correct
|
55
|
+
# form, going by the regular expression provided.You can require that the
|
56
|
+
# attribute matches the regular expression:
|
57
|
+
#
|
58
|
+
# class Person < ActiveRecord::Base
|
59
|
+
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Alternatively, you can require that the specified attribute does _not_
|
63
|
+
# match the regular expression:
|
64
|
+
#
|
65
|
+
# class Person < ActiveRecord::Base
|
66
|
+
# validates_format_of :email, without: /NOSPAM/
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# You can also provide a proc or lambda which will determine the regular
|
70
|
+
# expression that will be used to validate the attribute.
|
71
|
+
#
|
72
|
+
# class Person < ActiveRecord::Base
|
73
|
+
# # Admin can have number as a first letter in their screen name
|
74
|
+
# validates_format_of :screen_name,
|
75
|
+
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
|
79
|
+
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
|
80
|
+
#
|
81
|
+
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
|
82
|
+
# the <tt>multiline: true</tt> option in case you use any of these two
|
83
|
+
# anchors in the provided regular expression. In most cases, you should be
|
84
|
+
# using <tt>\A</tt> and <tt>\z</tt>.
|
85
|
+
#
|
86
|
+
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
|
87
|
+
# In addition, both must be a regular expression or a proc or lambda, or
|
88
|
+
# else an exception will be raised.
|
89
|
+
#
|
90
|
+
# Configuration options:
|
91
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
|
92
|
+
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the
|
93
|
+
# attribute is +nil+ (default is +false+).
|
94
|
+
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the
|
95
|
+
# attribute is blank (default is +false+).
|
96
|
+
# * <tt>:with</tt> - Regular expression that if the attribute matches will
|
97
|
+
# result in a successful validation. This can be provided as a proc or
|
98
|
+
# lambda returning regular expression which will be called at runtime.
|
99
|
+
# * <tt>:without</tt> - Regular expression that if the attribute does not
|
100
|
+
# match will result in a successful validation. This can be provided as
|
101
|
+
# a proc or lambda returning regular expression which will be called at
|
102
|
+
# runtime.
|
103
|
+
# * <tt>:multiline</tt> - Set to true if your regular expression contains
|
104
|
+
# anchors that match the beginning or end of lines as opposed to the
|
105
|
+
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
|
106
|
+
#
|
107
|
+
# There is also a list of default options supported by every validator:
|
108
|
+
# +:if+, +:unless+, +:on+ and +:strict+.
|
109
|
+
# See <tt>ActiveModel::Validation#validates</tt> for more information
|
110
|
+
def validates_format_of(*attr_names)
|
111
|
+
validates_with FormatValidator, _merge_attributes(attr_names)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
module HelperMethods
|
5
|
+
|
6
|
+
def _merge_attributes(attr_names)
|
7
|
+
options = Helpers._symbolyze_keys(Helpers._extract_options!(attr_names))
|
8
|
+
attr_names.flatten!
|
9
|
+
options[:attributes] = attr_names
|
10
|
+
options
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
|
3
|
+
# == Active \Model Length \Validator
|
4
|
+
module Validations
|
5
|
+
class LengthValidator < EachValidator # :nodoc:
|
6
|
+
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
|
7
|
+
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
|
8
|
+
|
9
|
+
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
if range = (options.delete(:in) || options.delete(:within))
|
13
|
+
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
|
14
|
+
options[:minimum], options[:maximum] = range.min, range.max
|
15
|
+
end
|
16
|
+
|
17
|
+
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
|
18
|
+
options[:minimum] = 1
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_validity!
|
25
|
+
keys = CHECKS.keys & options.keys
|
26
|
+
|
27
|
+
if keys.empty?
|
28
|
+
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
|
29
|
+
end
|
30
|
+
|
31
|
+
keys.each do |key|
|
32
|
+
value = options[key]
|
33
|
+
|
34
|
+
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
|
35
|
+
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_each(record, attribute, value)
|
41
|
+
value = tokenize(value)
|
42
|
+
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
|
43
|
+
errors_options = Helpers._except(options, *RESERVED_OPTIONS)
|
44
|
+
|
45
|
+
CHECKS.each do |key, validity_check|
|
46
|
+
next unless check_value = options[key]
|
47
|
+
|
48
|
+
if !value.nil? || skip_nil_check?(key)
|
49
|
+
next if value_length.send(validity_check, check_value)
|
50
|
+
end
|
51
|
+
|
52
|
+
errors_options[:count] = check_value
|
53
|
+
|
54
|
+
default_message = options[MESSAGES[key]]
|
55
|
+
errors_options[:message] ||= default_message if default_message
|
56
|
+
|
57
|
+
record.errors.add(attribute, MESSAGES[key], errors_options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def tokenize(value)
|
64
|
+
if options[:tokenizer] && value.kind_of?(String)
|
65
|
+
options[:tokenizer].call(value)
|
66
|
+
end || value
|
67
|
+
end
|
68
|
+
|
69
|
+
def skip_nil_check?(key)
|
70
|
+
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module HelperMethods
|
75
|
+
|
76
|
+
# Validates that the specified attribute matches the length restrictions
|
77
|
+
# supplied. Only one option can be used at a time:
|
78
|
+
#
|
79
|
+
# class Person < ActiveRecord::Base
|
80
|
+
# validates_length_of :first_name, maximum: 30
|
81
|
+
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
|
82
|
+
# validates_length_of :fax, in: 7..32, allow_nil: true
|
83
|
+
# validates_length_of :phone, in: 7..32, allow_blank: true
|
84
|
+
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
|
85
|
+
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
|
86
|
+
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
|
87
|
+
# validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
|
88
|
+
# tokenizer: ->(str) { str.scan(/\w+/) }
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# Configuration options:
|
92
|
+
# * <tt>:minimum</tt> - The minimum size of the attribute.
|
93
|
+
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
|
94
|
+
# default if not used with :minimum.
|
95
|
+
# * <tt>:is</tt> - The exact size of the attribute.
|
96
|
+
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
|
97
|
+
# the attribute.
|
98
|
+
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
|
99
|
+
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
100
|
+
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
101
|
+
# * <tt>:too_long</tt> - The error message if the attribute goes over the
|
102
|
+
# maximum (default is: "is too long (maximum is %{count} characters)").
|
103
|
+
# * <tt>:too_short</tt> - The error message if the attribute goes under the
|
104
|
+
# minimum (default is: "is too short (min is %{count} characters)").
|
105
|
+
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
|
106
|
+
# method and the attribute is the wrong size (default is: "is the wrong
|
107
|
+
# length (should be %{count} characters)").
|
108
|
+
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
|
109
|
+
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
|
110
|
+
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
111
|
+
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
|
112
|
+
# (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
|
113
|
+
# as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
|
114
|
+
# which counts individual characters.
|
115
|
+
#
|
116
|
+
# There is also a list of default options supported by every validator:
|
117
|
+
# +:if+, +:unless+, +:on+ and +:strict+.
|
118
|
+
# See <tt>ActiveModel::Validation#validates</tt> for more information
|
119
|
+
def validates_length_of(*attr_names)
|
120
|
+
validates_with LengthValidator, _merge_attributes(attr_names)
|
121
|
+
end
|
122
|
+
|
123
|
+
alias_method :validates_size_of, :validates_length_of
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
class PresenceValidator < EachValidator # :nodoc:
|
5
|
+
def validate_each(record, attr_name, value)
|
6
|
+
record.errors.add(attr_name, :blank, options) if Helpers._blank?(value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module HelperMethods
|
11
|
+
def validates_presence_of(*attr_names)
|
12
|
+
validates_with PresenceValidator, _merge_attributes(attr_names)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
class UniquenessValidator < EachValidator
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
|
7
|
+
return nil unless scope_method(record)
|
8
|
+
|
9
|
+
records.each do |other_record|
|
10
|
+
next if record == other_record || Helpers._marked_for_destruction?(other_record) || !scope_method(other_record)
|
11
|
+
|
12
|
+
if similar_objects?(record, other_record, attribute)
|
13
|
+
record.errors.add(attribute, :taken, options)
|
14
|
+
break
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
protected ########################### PROTECTED #######################
|
21
|
+
|
22
|
+
def records
|
23
|
+
[*base.target].inject([]) do |scoped_list, object|
|
24
|
+
scoped_list << object if scope_method(object)
|
25
|
+
scoped_list
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def similar_objects?(record, other_record, attribute)
|
30
|
+
if options.key?(:conditions)
|
31
|
+
if base.method(options[:conditions]).arity == 3
|
32
|
+
base.send(options[:conditions], record, other_record, attribute)
|
33
|
+
else
|
34
|
+
base.send(options[:conditions], record, other_record)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
record.send(attribute) == other_record.send(attribute)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private ###################### PRIVATE ####################
|
42
|
+
|
43
|
+
def scope_method(object)
|
44
|
+
options.key?(:scope) ? base.send(options[:scope], object) : true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
module HelperMethods
|
50
|
+
def validates_uniqueness_of(*attr_names)
|
51
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module UseCaseValidations
|
2
|
+
|
3
|
+
class Validator
|
4
|
+
|
5
|
+
attr_reader :options
|
6
|
+
attr_accessor :base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@options = Helpers._except(options, :class).freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
# Override this method in subclasses with validation logic, adding errors
|
13
|
+
# to the records +errors+ array where necessary.
|
14
|
+
def validate(record)
|
15
|
+
raise NotImplementedError, "Subclasses must implement a validate(record) method."
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'usecasing'
|
2
|
+
|
3
|
+
require "usecasing_validations/target"
|
4
|
+
require "usecasing_validations/helpers"
|
5
|
+
require "usecasing_validations/errors"
|
6
|
+
require "usecasing_validations/validator"
|
7
|
+
require "usecasing_validations/each_validator"
|
8
|
+
require "usecasing_validations/custom_validator"
|
9
|
+
|
10
|
+
require "usecasing_validations/validations/helper_methods"
|
11
|
+
require "usecasing_validations/validations/format"
|
12
|
+
require "usecasing_validations/validations/length"
|
13
|
+
require "usecasing_validations/validations/presence"
|
14
|
+
require "usecasing_validations/validations/uniqueness"
|
15
|
+
|
16
|
+
|
17
|
+
module UseCase
|
18
|
+
autoload :Validator, 'usecasing/validator'
|
19
|
+
autoload :DependsAll, 'usecasing/depends_all'
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
module UseCaseValidations
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.extend(Validations::HelperMethods)
|
27
|
+
base.class_eval { include Target }
|
28
|
+
base.extend(ClassMethods)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected #################### PROTECTED ######################
|
32
|
+
|
33
|
+
def run_validations!(object_to_validate)
|
34
|
+
self.class.validators.each do |validator|
|
35
|
+
next unless option_if_succeeds(validator, object_to_validate)
|
36
|
+
|
37
|
+
validator.base = self
|
38
|
+
validator.validate(object_to_validate)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?(object_to_validate)
|
43
|
+
extend_errors_if_necessary object_to_validate
|
44
|
+
|
45
|
+
object_to_validate.errors.clear if self.class.clear_errors?
|
46
|
+
|
47
|
+
run_validations! object_to_validate
|
48
|
+
|
49
|
+
object_to_validate.errors.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
private ######################## PRIVATE #######################
|
53
|
+
|
54
|
+
def extend_errors_if_necessary(object_to_validate)
|
55
|
+
return true if object_to_validate.respond_to?(:errors)
|
56
|
+
|
57
|
+
object_to_validate.instance_eval do
|
58
|
+
def errors; @errors ||= Errors.new(self); end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def option_if_succeeds(validator, object_to_validate)
|
63
|
+
if validator.options.key?(:if)
|
64
|
+
Helpers._call_proc_or_method(self, validator.options[:if], object_to_validate)
|
65
|
+
else
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
def clear_errors!
|
73
|
+
@clear_errors = true
|
74
|
+
end
|
75
|
+
|
76
|
+
def clear_errors?
|
77
|
+
defined?(@clear_errors) ? @clear_errors : false
|
78
|
+
end
|
79
|
+
|
80
|
+
def _validators
|
81
|
+
@_validators ||= Hash.new { |h,k| h[k] = [] }
|
82
|
+
end
|
83
|
+
|
84
|
+
def _validators=(value)
|
85
|
+
@_validators = value
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate(*args, &block)
|
89
|
+
_validators[nil] << CustomValidator.new(args, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def validates_with(*args, &block)
|
93
|
+
options = Helpers._extract_options!(args)
|
94
|
+
options[:class] = self
|
95
|
+
|
96
|
+
args.each do |klass|
|
97
|
+
validator = klass.new(options, &block)
|
98
|
+
|
99
|
+
if validator.respond_to?(:attributes) && !validator.attributes.empty?
|
100
|
+
validator.attributes.each do |attribute|
|
101
|
+
_validators[attribute.to_sym] << validator
|
102
|
+
end
|
103
|
+
else
|
104
|
+
_validators[nil] << validator
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def validators
|
110
|
+
_validators.values.flatten.uniq
|
111
|
+
end
|
112
|
+
|
113
|
+
# Copy validators on inheritance.
|
114
|
+
def inherited(base)
|
115
|
+
dup = _validators.dup
|
116
|
+
base._validators = dup.each { |k, v| dup[k] = v.dup }
|
117
|
+
super
|
118
|
+
end
|
119
|
+
|
120
|
+
end #/ClassMethods
|
121
|
+
|
122
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'usecasing_validations'
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
Dir.chdir("spec/") do
|
6
|
+
Dir["support/models/*.rb"].each { |file| require file }
|
7
|
+
Dir["support/usecases/*.rb"].each { |file| require file }
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
config.mock_framework = :mocha
|
15
|
+
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class RubyPostWithComments
|
2
|
+
|
3
|
+
class RubyComment
|
4
|
+
|
5
|
+
attr_accessor :title, :email, :post_id
|
6
|
+
|
7
|
+
def initialize(attributes = {})
|
8
|
+
(attributes || {}).each { |name, value| send("#{name}=", value) }
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
attr_reader :comments
|
15
|
+
|
16
|
+
def initialize(comments = {})
|
17
|
+
@comments = comments.map { |comment| RubyComment.new(comment) }
|
18
|
+
@comments = [] if @comments.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def first_two_comments
|
22
|
+
[
|
23
|
+
comments[0],
|
24
|
+
comments[1]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ValidateComments < UseCase::Validator
|
2
|
+
|
3
|
+
target :comments, in: :post
|
4
|
+
|
5
|
+
validates_presence_of :title, message: "can't be blank!"
|
6
|
+
|
7
|
+
validates_format_of :email, with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, message: "invalid format!"
|
8
|
+
|
9
|
+
validates_length_of :title, minimum: 6, allow_nil: true
|
10
|
+
|
11
|
+
validate :custom_validation1, :custom_validation2
|
12
|
+
|
13
|
+
|
14
|
+
protected ######################### PROTECTED ############################
|
15
|
+
|
16
|
+
def custom_validation1(comment)
|
17
|
+
if ['force_error1', 'force_error2'].include?(comment.title)
|
18
|
+
comment.errors.add(:title, 'custom_validation1')
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def custom_validation2(comment)
|
26
|
+
if comment.email == "force_error@gmail.com"
|
27
|
+
comment.errors.add(:email, 'custom_validation2')
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ValidateCommentsCustomTarget < UseCase::Validator
|
2
|
+
|
3
|
+
def target
|
4
|
+
[
|
5
|
+
context.post.comments[0],
|
6
|
+
context.post.comments[1]
|
7
|
+
]
|
8
|
+
end
|
9
|
+
|
10
|
+
validates_presence_of :title
|
11
|
+
|
12
|
+
validates_format_of :email, with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Group
|
2
|
+
|
3
|
+
class ValidatePostTitle < UseCase::Validator
|
4
|
+
|
5
|
+
target :post
|
6
|
+
|
7
|
+
validates_presence_of :title
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class ValidatePostBody < UseCase::Validator
|
12
|
+
|
13
|
+
target :post
|
14
|
+
|
15
|
+
validates_presence_of :body
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class ValidateDependsAll < UseCase::DependsAll
|
20
|
+
|
21
|
+
depends ValidatePostTitle, ValidatePostBody
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ValidatePost < UseCase::Validator
|
2
|
+
|
3
|
+
target :post
|
4
|
+
|
5
|
+
validates_presence_of :title, :body, message: "can't be blank!"
|
6
|
+
|
7
|
+
validates_presence_of :phone_number, if: ->(post) { context.validate_phone_number }
|
8
|
+
|
9
|
+
validates_format_of :phone_number, with: /\A[0-9 ]*\z/, message: "invalid format!", if: :validate_phone_number
|
10
|
+
|
11
|
+
|
12
|
+
protected ###################### PROTECTED ####################
|
13
|
+
|
14
|
+
def validate_phone_number(post)
|
15
|
+
context.validate_phone_number
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|