interactor-validation 0.4.0 → 0.4.1
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/README.md +78 -6
- data/lib/interactor/validation/validates.rb +73 -29
- data/lib/interactor/validation/version.rb +1 -1
- 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: a84c031fc1b57751f8632449ffdc9014c2dfa7caff24bafd5bf418882adf1550
|
|
4
|
+
data.tar.gz: a3315d47373378b4152095b7e358305330c4aa67af41f3f97e943092b0f5ed23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39c3fc71fc57f9b56c342417fba7b362f095516e9e624a810f6474b6f4006173de76c6336be8dbaaf067b6d30bfac916c5628312b8f74bbbe5536e01837c4b28
|
|
7
|
+
data.tar.gz: 68e581fd48735e33046850215381a91687d040b6d792d03e4d0972772023fa099978441908b6f460367cc909d5175bb60992753c635cc001199072a5468cb8a3
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Structured, lightweight parameter validation designed specifically for [Interact
|
|
|
7
7
|
- **Built for Interactor** - Seamless integration with service objects
|
|
8
8
|
- **Comprehensive validators** - Presence, format, length, inclusion, numericality, boolean
|
|
9
9
|
- **Nested validation** - Validate complex hashes and arrays
|
|
10
|
-
- **Custom validations** -
|
|
10
|
+
- **Custom validations** - `validate!` for other business logic
|
|
11
11
|
- **Flexible error formats** - Human-readable messages or machine-readable codes
|
|
12
12
|
- **Zero dependencies** - Just Interactor and Ruby stdlib
|
|
13
13
|
- **Configurable** - Control validation behavior and error handling
|
|
@@ -30,6 +30,9 @@ Structured, lightweight parameter validation designed specifically for [Interact
|
|
|
30
30
|
- [Parameter Delegation](#parameter-delegation)
|
|
31
31
|
- [Requirements](#requirements)
|
|
32
32
|
- [Design Philosophy](#design-philosophy)
|
|
33
|
+
- [Development](#development)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [License](#license)
|
|
33
36
|
|
|
34
37
|
## Installation
|
|
35
38
|
|
|
@@ -239,7 +242,7 @@ result.errors
|
|
|
239
242
|
|
|
240
243
|
## Custom Validations
|
|
241
244
|
|
|
242
|
-
Override `validate!` for custom business logic:
|
|
245
|
+
Override `validate!` for custom business logic that requires external dependencies (database queries, API calls, etc.):
|
|
243
246
|
|
|
244
247
|
```ruby
|
|
245
248
|
class CreateOrder
|
|
@@ -253,7 +256,8 @@ class CreateOrder
|
|
|
253
256
|
validates :user_id, presence: true
|
|
254
257
|
|
|
255
258
|
def validate!
|
|
256
|
-
|
|
259
|
+
# Parameter validations have already run at this point
|
|
260
|
+
# No need to call super - there is no parent validate! method
|
|
257
261
|
|
|
258
262
|
product = Product.find_by(id: product_id)
|
|
259
263
|
if product.nil?
|
|
@@ -269,9 +273,41 @@ class CreateOrder
|
|
|
269
273
|
end
|
|
270
274
|
```
|
|
271
275
|
|
|
276
|
+
**Important:** Parameter validations (defined via `validates`) run automatically before `validate!`. You should never call `super` in your `validate!` method as there is no parent implementation.
|
|
277
|
+
|
|
272
278
|
## Configuration
|
|
273
279
|
|
|
274
|
-
|
|
280
|
+
Configuration can be set at three levels (in order of precedence):
|
|
281
|
+
|
|
282
|
+
### 1. Per-Interactor Configuration
|
|
283
|
+
|
|
284
|
+
Configure individual interactors using either a `configure` block or dedicated methods:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
class CreateUser
|
|
288
|
+
include Interactor
|
|
289
|
+
include Interactor::Validation
|
|
290
|
+
|
|
291
|
+
# Option 1: Using configure block
|
|
292
|
+
configure do |config|
|
|
293
|
+
config.halt = true
|
|
294
|
+
config.mode = :code
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Option 2: Using dedicated methods
|
|
298
|
+
validation_halt true
|
|
299
|
+
validation_mode :code
|
|
300
|
+
validation_skip_validate false
|
|
301
|
+
|
|
302
|
+
# ... validations and call method
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Configuration is inherited from parent classes and can be overridden in child classes.
|
|
307
|
+
|
|
308
|
+
### 2. Global Configuration
|
|
309
|
+
|
|
310
|
+
Configure global defaults in an initializer or before your interactors are loaded:
|
|
275
311
|
|
|
276
312
|
```ruby
|
|
277
313
|
Interactor::Validation.configure do |config|
|
|
@@ -427,10 +463,46 @@ While ActiveModel::Validations is powerful, it's designed for ActiveRecord model
|
|
|
427
463
|
|
|
428
464
|
## Development
|
|
429
465
|
|
|
466
|
+
### Setup
|
|
467
|
+
|
|
430
468
|
```bash
|
|
431
469
|
bundle install
|
|
432
|
-
|
|
433
|
-
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Running Tests
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
bundle exec rspec # Run all tests
|
|
476
|
+
bundle exec rspec spec/interactor/validation_spec.rb # Run specific test file
|
|
477
|
+
bundle exec rspec spec/interactor/validation_spec.rb:42 # Run specific test at line 42
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Linting
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
bundle exec rubocop # Check code style
|
|
484
|
+
bundle exec rubocop -a # Auto-fix safe issues
|
|
485
|
+
bundle exec rubocop -A # Auto-fix all issues (use with caution)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Combined (Default Rake Task)
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
bundle exec rake # Runs both rspec and rubocop
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Interactive Console
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
bundle exec irb -r ./lib/interactor/validation # Load gem in IRB
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Gem Management
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
bundle exec rake build # Build gem file
|
|
504
|
+
bundle exec rake install # Install gem locally
|
|
505
|
+
bundle exec rake release # Release gem (requires permissions)
|
|
434
506
|
```
|
|
435
507
|
|
|
436
508
|
## Contributing
|
|
@@ -11,6 +11,9 @@ require_relative "validators/array"
|
|
|
11
11
|
|
|
12
12
|
module Interactor
|
|
13
13
|
module Validation
|
|
14
|
+
# Exception raised when validation should halt on first error
|
|
15
|
+
class HaltValidation < StandardError; end
|
|
16
|
+
|
|
14
17
|
module Validates
|
|
15
18
|
def self.included(base)
|
|
16
19
|
base.extend(ClassMethods)
|
|
@@ -21,18 +24,73 @@ module Interactor
|
|
|
21
24
|
base.prepend(InstanceMethods)
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
class ConfigurationProxy
|
|
28
|
+
def initialize(config_hash)
|
|
29
|
+
@config = config_hash
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def mode=(value)
|
|
33
|
+
@config[:mode] = value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def halt=(value)
|
|
37
|
+
@config[:halt] = value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def skip_validate=(value)
|
|
41
|
+
@config[:skip_validate] = value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
24
45
|
module ClassMethods
|
|
46
|
+
def inherited(subclass)
|
|
47
|
+
super
|
|
48
|
+
# Ensure child class gets its own copy of config, merging with parent's config
|
|
49
|
+
subclass._validation_config = _validation_config.dup
|
|
50
|
+
# Ensure child class gets its own copy of validations
|
|
51
|
+
subclass._validations = _validations.dup
|
|
52
|
+
end
|
|
53
|
+
|
|
25
54
|
def validates(param_name, **rules, &)
|
|
55
|
+
# Ensure we have our own copy of validations when first modifying
|
|
56
|
+
begin
|
|
57
|
+
self._validations = _validations.dup if _validations.equal?(superclass._validations)
|
|
58
|
+
rescue StandardError
|
|
59
|
+
false
|
|
60
|
+
end
|
|
26
61
|
_validations[param_name] ||= {}
|
|
27
62
|
_validations[param_name].merge!(rules)
|
|
28
63
|
_validations[param_name][:_nested] = build_nested_rules(&) if block_given?
|
|
29
64
|
end
|
|
30
65
|
|
|
66
|
+
def configure
|
|
67
|
+
# Ensure we have our own copy of config before modifying
|
|
68
|
+
begin
|
|
69
|
+
self._validation_config = _validation_config.dup if _validation_config.equal?(superclass._validation_config)
|
|
70
|
+
rescue StandardError
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
config = ConfigurationProxy.new(_validation_config)
|
|
74
|
+
yield(config)
|
|
75
|
+
end
|
|
76
|
+
|
|
31
77
|
def validation_halt(value)
|
|
78
|
+
# Ensure we have our own copy of config before modifying
|
|
79
|
+
begin
|
|
80
|
+
self._validation_config = _validation_config.dup if _validation_config.equal?(superclass._validation_config)
|
|
81
|
+
rescue StandardError
|
|
82
|
+
false
|
|
83
|
+
end
|
|
32
84
|
_validation_config[:halt] = value
|
|
33
85
|
end
|
|
34
86
|
|
|
35
87
|
def validation_mode(value)
|
|
88
|
+
# Ensure we have our own copy of config before modifying
|
|
89
|
+
begin
|
|
90
|
+
self._validation_config = _validation_config.dup if _validation_config.equal?(superclass._validation_config)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
false
|
|
93
|
+
end
|
|
36
94
|
_validation_config[:mode] = value
|
|
37
95
|
end
|
|
38
96
|
|
|
@@ -57,19 +115,8 @@ module Interactor
|
|
|
57
115
|
end
|
|
58
116
|
end
|
|
59
117
|
|
|
60
|
-
# Base module with default validate! that does nothing
|
|
61
|
-
module BaseValidation
|
|
62
|
-
def validate!
|
|
63
|
-
# Default implementation - does nothing
|
|
64
|
-
# Subclasses can override and call super
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
118
|
module InstanceMethods
|
|
69
119
|
def self.prepended(base)
|
|
70
|
-
# Include BaseValidation so super works in user's validate!
|
|
71
|
-
base.include(BaseValidation) unless base.ancestors.include?(BaseValidation)
|
|
72
|
-
|
|
73
120
|
# Include all validator modules
|
|
74
121
|
base.include(Validators::Presence)
|
|
75
122
|
base.include(Validators::Numeric)
|
|
@@ -82,31 +129,28 @@ module Interactor
|
|
|
82
129
|
end
|
|
83
130
|
|
|
84
131
|
def errors
|
|
85
|
-
@errors ||= Errors.new
|
|
132
|
+
@errors ||= Errors.new(halt_checker: -> { validation_config(:halt) })
|
|
86
133
|
end
|
|
87
134
|
|
|
88
|
-
def
|
|
89
|
-
errors.clear
|
|
135
|
+
def run_validations!
|
|
90
136
|
param_errors = false
|
|
91
137
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
self.class._validations
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Halt on first error if configured
|
|
99
|
-
if validation_config(:halt) && errors.any?
|
|
100
|
-
context.fail!(errors: format_errors)
|
|
101
|
-
break
|
|
138
|
+
begin
|
|
139
|
+
# Run parameter validations
|
|
140
|
+
if self.class._validations
|
|
141
|
+
self.class._validations.each do |param, rules|
|
|
142
|
+
value = context.respond_to?(param) ? context.public_send(param) : nil
|
|
143
|
+
validate_param(param, value, rules)
|
|
102
144
|
end
|
|
145
|
+
param_errors = errors.any?
|
|
103
146
|
end
|
|
104
|
-
param_errors = errors.any?
|
|
105
|
-
end
|
|
106
147
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
# Run custom validations if defined
|
|
149
|
+
# Skip if param validations failed and skip_validate is true
|
|
150
|
+
validate! if respond_to?(:validate!, true) && !(param_errors && validation_config(:skip_validate))
|
|
151
|
+
rescue HaltValidation
|
|
152
|
+
# Validation halted on first error - fall through to fail context
|
|
153
|
+
end
|
|
110
154
|
|
|
111
155
|
# Fail context if any errors exist
|
|
112
156
|
context.fail!(errors: format_errors) if errors.any?
|