usecasing_validations 0.5.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.
- 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
|