interactor-validation 0.3.1 → 0.3.2
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 +4 -4
- data/lib/interactor/validation/validates.rb +92 -11
- data/lib/interactor/validation/version.rb +1 -1
- data/lib/interactor/validation.rb +8 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 553f0d597d013bf2fa42f8225ead3b75fea6879b1519724d1d800e6f83db8319
|
|
4
|
+
data.tar.gz: f9eb147c94a9cd17a0bc453feb912a90edfaee8f87ec336db7b4f6c1af99b3fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 124fbda7650b19706d984230a8aecd47af6c272096f26300120564e94f7664e58baceb48521909e025f621bf41e77c647a27c6a0bdc843c367d03812b36afaee
|
|
7
|
+
data.tar.gz: d188ba13516259565e742bcb1ef558b10022436823ee1c183250b582f023ead80a40236c4c33b1e6fc7a2362d13178cd0c19f88728bb6c12911bf05a09a12861
|
|
@@ -21,9 +21,11 @@ module Interactor
|
|
|
21
21
|
|
|
22
22
|
def self.included(base)
|
|
23
23
|
super
|
|
24
|
-
# Include ActiveModel::Validations first, then prepend our
|
|
24
|
+
# Include ActiveModel::Validations first, then prepend our overrides
|
|
25
25
|
base.include ActiveModel::Validations unless base.included_modules.include?(ActiveModel::Validations)
|
|
26
26
|
base.singleton_class.prepend(ClassMethodsOverride)
|
|
27
|
+
# Prepend our instance method overrides to take precedence over ActiveModel
|
|
28
|
+
base.prepend(InstanceMethodsOverride)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
module ClassMethodsOverride
|
|
@@ -88,11 +90,75 @@ module Interactor
|
|
|
88
90
|
end
|
|
89
91
|
end
|
|
90
92
|
|
|
93
|
+
# Module prepended to override ActiveModel instance methods
|
|
94
|
+
module InstanceMethodsOverride
|
|
95
|
+
# Override ActiveModel's validate! to prevent exception-raising behavior
|
|
96
|
+
# This hook is called automatically after validate_params!
|
|
97
|
+
# Users can override this to add custom validation logic
|
|
98
|
+
# @return [void]
|
|
99
|
+
# @example
|
|
100
|
+
# def validate!
|
|
101
|
+
# super # Optional: call parent implementation if needed
|
|
102
|
+
# errors.add(:base, "Custom error") if some_condition?
|
|
103
|
+
# end
|
|
104
|
+
def validate!
|
|
105
|
+
# Preserve errors from validate_params! before calling super
|
|
106
|
+
# because super might trigger ActiveModel's valid? which clears errors
|
|
107
|
+
existing_error_details = errors.map do |error|
|
|
108
|
+
begin
|
|
109
|
+
{ attribute: error.attribute, type: error.type, options: error.options }
|
|
110
|
+
rescue ArgumentError => e
|
|
111
|
+
# For anonymous classes, accessing error properties may fail
|
|
112
|
+
if e.message.include?("Class name cannot be blank")
|
|
113
|
+
{ attribute: error.attribute, type: :invalid, options: {} }
|
|
114
|
+
else
|
|
115
|
+
raise
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Call super to allow class's validate! to run and add custom errors
|
|
121
|
+
# Rescue exceptions that might be raised
|
|
122
|
+
begin
|
|
123
|
+
super
|
|
124
|
+
rescue NoMethodError
|
|
125
|
+
# No parent validate! method, which is fine
|
|
126
|
+
rescue ActiveModel::ValidationError
|
|
127
|
+
# ActiveModel's validate! raises this when there are errors
|
|
128
|
+
# We handle errors differently, so just ignore this exception
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Restore errors from validate_params! and add any new ones from custom validate!
|
|
132
|
+
existing_error_details.each do |error_detail|
|
|
133
|
+
# Only add if not already present (avoid duplicates)
|
|
134
|
+
unless errors.where(error_detail[:attribute], error_detail[:type]).any?
|
|
135
|
+
begin
|
|
136
|
+
errors.add(error_detail[:attribute], error_detail[:type], **error_detail[:options])
|
|
137
|
+
rescue ArgumentError => e
|
|
138
|
+
# For anonymous classes, fall back to adding with message directly
|
|
139
|
+
raise unless e.message.include?("Class name cannot be blank")
|
|
140
|
+
|
|
141
|
+
message = error_detail[:options][:message] || error_detail[:type].to_s
|
|
142
|
+
errors.add(error_detail[:attribute], message)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Check all accumulated errors from validate_params! and this hook
|
|
148
|
+
# Fail the context if any errors exist
|
|
149
|
+
return if errors.empty?
|
|
150
|
+
|
|
151
|
+
# Use the existing formatted_errors method which handles all the edge cases
|
|
152
|
+
context.fail!(errors: formatted_errors)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
91
156
|
private
|
|
92
157
|
|
|
93
158
|
# Validates all declared parameters before execution
|
|
159
|
+
# Accumulates errors but does not fail the context
|
|
160
|
+
# The validate! hook will fail the context if there are any errors
|
|
94
161
|
# @return [void]
|
|
95
|
-
# @raise [Interactor::Failure] if validation fails
|
|
96
162
|
def validate_params!
|
|
97
163
|
# Memoize config for performance
|
|
98
164
|
@current_config = current_config
|
|
@@ -101,9 +167,12 @@ module Interactor
|
|
|
101
167
|
instrument("validate_params.interactor_validation") do
|
|
102
168
|
# Trigger ActiveModel validations first (validate callbacks)
|
|
103
169
|
# This runs any custom validations defined with validate :method_name
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
170
|
+
begin
|
|
171
|
+
valid?
|
|
172
|
+
rescue ArgumentError => e
|
|
173
|
+
# For anonymous classes, valid? may fail when generating messages
|
|
174
|
+
raise unless e.message.include?("Class name cannot be blank")
|
|
175
|
+
end
|
|
107
176
|
|
|
108
177
|
# Run our custom param validations after ActiveModel validations
|
|
109
178
|
self.class._param_validations.each do |param_name, rules|
|
|
@@ -115,9 +184,8 @@ module Interactor
|
|
|
115
184
|
break if @current_config.halt_on_first_error && errors.any?
|
|
116
185
|
end
|
|
117
186
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
context.fail!(errors: formatted_errors)
|
|
187
|
+
# Don't fail here - let validate! hook handle failure
|
|
188
|
+
# This allows validate! to run and add additional custom errors
|
|
121
189
|
end
|
|
122
190
|
ensure
|
|
123
191
|
@current_config = nil # Clear memoization
|
|
@@ -628,9 +696,14 @@ module Interactor
|
|
|
628
696
|
errors.map do |error|
|
|
629
697
|
# Convert attribute path to uppercase, handling nested paths
|
|
630
698
|
# Example: "attributes.username" -> "ATTRIBUTES.USERNAME"
|
|
631
|
-
# Example: "attributes[0].username" -> "ATTRIBUTES[0]
|
|
699
|
+
# Example: "attributes[0].username" -> "ATTRIBUTES[0]_USERNAME"
|
|
632
700
|
param_name = format_attribute_for_code(error.attribute)
|
|
633
|
-
|
|
701
|
+
begin
|
|
702
|
+
message = error.message
|
|
703
|
+
rescue ArgumentError, NoMethodError => e
|
|
704
|
+
# For anonymous classes or other edge cases, fall back to type
|
|
705
|
+
message = error.type.to_s.upcase
|
|
706
|
+
end
|
|
634
707
|
|
|
635
708
|
{ code: "#{param_name}_#{message}" }
|
|
636
709
|
end
|
|
@@ -672,7 +745,15 @@ module Interactor
|
|
|
672
745
|
"#{attribute_name} #{error_message}"
|
|
673
746
|
elsif error.respond_to?(:message)
|
|
674
747
|
# Try to use ActiveModel's message for simple attributes
|
|
675
|
-
error.message
|
|
748
|
+
message = error.message
|
|
749
|
+
# If translation is missing, fall back to our default messages
|
|
750
|
+
if message.include?("Translation missing")
|
|
751
|
+
attribute_name = error.attribute.to_s.humanize
|
|
752
|
+
error_message = error.options[:message] || default_message_for_type(error.type, error.options)
|
|
753
|
+
"#{attribute_name} #{error_message}"
|
|
754
|
+
else
|
|
755
|
+
message
|
|
756
|
+
end
|
|
676
757
|
end
|
|
677
758
|
rescue ArgumentError, NoMethodError
|
|
678
759
|
# Fallback for anonymous classes or other issues
|
|
@@ -38,15 +38,21 @@ module Interactor
|
|
|
38
38
|
|
|
39
39
|
def self.included(base)
|
|
40
40
|
super
|
|
41
|
-
# Set up the validation
|
|
41
|
+
# Set up the validation hooks after all modules are included
|
|
42
42
|
# Use class_eval to ensure we're in the right context
|
|
43
43
|
base.class_eval do
|
|
44
|
+
# Register both validate_params! and validate! hooks
|
|
45
|
+
# Parameter validations run first, then custom validate! hook
|
|
44
46
|
before :validate_params! if respond_to?(:before)
|
|
47
|
+
before :validate! if respond_to?(:before)
|
|
45
48
|
|
|
46
|
-
# Set up inherited hook to ensure child classes also get the before
|
|
49
|
+
# Set up inherited hook to ensure child classes also get the before hooks
|
|
47
50
|
def self.inherited(subclass)
|
|
48
51
|
super
|
|
49
52
|
subclass.before :validate_params! if subclass.respond_to?(:before)
|
|
53
|
+
subclass.before :validate! if subclass.respond_to?(:before)
|
|
54
|
+
# Also prepend InstanceMethodsOverride to child classes
|
|
55
|
+
subclass.prepend(Interactor::Validation::Validates::InstanceMethodsOverride)
|
|
50
56
|
end
|
|
51
57
|
end
|
|
52
58
|
end
|