interactor-validation 0.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8c04f34a47f6d887079d08a07958d91c19932a1b3c190faaa0ba4f1a85dbbf2
4
- data.tar.gz: 167cbca80c87fe0519f3bab2a1e36c4d753366126e5587d3559907473d558812
3
+ metadata.gz: 553f0d597d013bf2fa42f8225ead3b75fea6879b1519724d1d800e6f83db8319
4
+ data.tar.gz: f9eb147c94a9cd17a0bc453feb912a90edfaee8f87ec336db7b4f6c1af99b3fb
5
5
  SHA512:
6
- metadata.gz: 03a955748e4429b6e141a17292484ec49bf52c5a8af056c7436270f91a9a536c1e782a6ca7d3af38b4437736dd960491735eb233c4abe3599d96a604d9297966
7
- data.tar.gz: 0ba87d4e8591ff0ba7cb260d10bda55c8d0526bb8de75cc66a7c81b0addf2d5355d4f9da9919e3ad76fe3052e43868d7af39eb5d57729319ed92500ad12c5e61
6
+ metadata.gz: 124fbda7650b19706d984230a8aecd47af6c272096f26300120564e94f7664e58baceb48521909e025f621bf41e77c647a27c6a0bdc843c367d03812b36afaee
7
+ data.tar.gz: d188ba13516259565e742bcb1ef558b10022436823ee1c183250b582f023ead80a40236c4c33b1e6fc7a2362d13178cd0c19f88728bb6c12911bf05a09a12861
data/README.md CHANGED
@@ -165,7 +165,11 @@ Configure behavior for all interactors:
165
165
  ```ruby
166
166
  # config/initializers/interactor_validation.rb
167
167
  Interactor::Validation.configure do |config|
168
- # Error format: :default (ActiveModel-style) or :code (structured codes)
168
+ # Error format mode - Available options:
169
+ # :default - ActiveModel-style messages (default)
170
+ # { attribute: :email, type: :blank, message: "Email can't be blank" }
171
+ # :code - Structured error codes for APIs
172
+ # { code: "EMAIL_IS_REQUIRED" }
169
173
  config.error_mode = :default
170
174
 
171
175
  # Stop at first error for better performance
@@ -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 override
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
- # NOTE: valid? must be called BEFORE adding our custom errors
105
- # because it clears the errors object
106
- valid?
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
- return if errors.empty?
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].USERNAME"
699
+ # Example: "attributes[0].username" -> "ATTRIBUTES[0]_USERNAME"
632
700
  param_name = format_attribute_for_code(error.attribute)
633
- message = error.message
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Interactor
4
4
  module Validation
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.2"
6
6
  end
7
7
  end
@@ -38,15 +38,21 @@ module Interactor
38
38
 
39
39
  def self.included(base)
40
40
  super
41
- # Set up the validation hook after all modules are included
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 hook
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interactor-validation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wilson Anciro