lite-validation 0.0.1 → 0.0.3
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/.rubocop.yml +3 -0
- data/README.md +68 -35
- data/bench/comparative/transformation.rb +98 -0
- data/bench/comparative/validation.rb +205 -0
- data/lib/lite/validation/validator/node/abstract.rb +2 -0
- data/lib/lite/validation/validator/node/implementation/apply_ruling.rb +23 -15
- data/lib/lite/validation/validator/node/implementation/helpers/yield_strategy.rb +2 -2
- data/lib/lite/validation/validator/node/implementation/navigation.rb +1 -1
- data/lib/lite/validation/validator/node/implementation/transformation.rb +49 -0
- data/lib/lite/validation/validator/node/implementation/validation.rb +1 -1
- data/lib/lite/validation/version.rb +1 -1
- metadata +5 -3
- data/bench/comparative.rb +0 -197
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72bde3b3c35381247baebe84b4b4d4394bf33769c4b5187e4d7a1124ca64464a
|
|
4
|
+
data.tar.gz: 4e97554d20e8bb5986a2d79b35483ad2b22ffa4c52ec874902108d1d37c77d0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f80e113ea3e07a76dd9b23a61c5e85a6302054f00c105d8efabbab4acf2077dbdd622f61988d78748374fa8d31d24a0ebdd158092b9dde824f4af359d634ee0
|
|
7
|
+
data.tar.gz: 332a9722829258a3631d84b12b432599b5d1722884273f81c33c40d8fb146d69e95adfabf10d468bfd29e53b3adda211270bfad49e29d4cc2886f08e34f2beea
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -5,20 +5,20 @@ transform input into new shape through an immutable, composable interface
|
|
|
5
5
|
that treats validation as a general-purpose computational tool.
|
|
6
6
|
|
|
7
7
|
- Extensible wrapper system supports custom collections (like `ActiveRecord::Relation`)
|
|
8
|
-
- Pluggable predicate engines
|
|
8
|
+
- Pluggable predicate engines – ships with `Dry::Logic` adapter for declarative validation
|
|
9
9
|
- Configurable result types and error formats
|
|
10
10
|
- Transform data while validating through integrated commit/transformation mechanics
|
|
11
11
|
|
|
12
12
|
Engineered for consistent performance regardless of validation outcome.
|
|
13
13
|
This makes it ideal for high-throughput scenarios where validation serves
|
|
14
|
-
as filtering, decision-making, or data processing logic
|
|
14
|
+
as filtering, decision-making, or data processing logic – not just input sanitization.
|
|
15
15
|
Whether validating inputs that mostly pass or mostly fail, performance remains predictable.
|
|
16
16
|
Perfect for applications that need validation throughout the system:
|
|
17
17
|
API endpoints, background jobs, data pipelines, and anywhere you need reliable
|
|
18
18
|
validation with transformation capabilities.
|
|
19
19
|
|
|
20
20
|
## Getting started
|
|
21
|
-
Before validating data, you'll need to create a **coordinator**
|
|
21
|
+
Before validating data, you'll need to create a **coordinator** –
|
|
22
22
|
a configuration object that defines how the validator integrates
|
|
23
23
|
with your application. The coordinator specifies what types
|
|
24
24
|
to use for results, options, and errors, making the library adaptable
|
|
@@ -79,18 +79,18 @@ expect(result.failure).to match({ errors: [have_attributes(code: :excessive)] })
|
|
|
79
79
|
|
|
80
80
|
The core of validation is the `validate` method and its counterpart `validate?`.
|
|
81
81
|
These methods expose the current value and context to your validation block,
|
|
82
|
-
expecting a ruling in return
|
|
82
|
+
expecting a ruling in return – a decision about the value's validity.
|
|
83
83
|
There are four types of ruling available in validate blocks:
|
|
84
|
-
- `Pass()`
|
|
84
|
+
- `Pass()` – Indicates the value is valid. Rarely used since returning
|
|
85
85
|
`nil` has the same effect.
|
|
86
|
-
- `Dispute(code, message: nil, data: nil)`
|
|
86
|
+
- `Dispute(code, message: nil, data: nil)` – Marks the value as invalid but allows validation
|
|
87
87
|
to continue on this node. All ancestor nodes also become disputed. You can also pass
|
|
88
88
|
a structured error object: `Dispute(structured_error)`
|
|
89
|
-
- `Refute(code, message: nil, data: nil)`
|
|
89
|
+
- `Refute(code, message: nil, data: nil)` – Marks the value as invalid with a fatal error
|
|
90
90
|
that stops further validation on this node. Parent nodes become disputed unless this
|
|
91
91
|
occurs in a [critical section](#critical-section). Also accepts structured errors: `Refute(structured_error)`.
|
|
92
|
-
- `Commit(value)`
|
|
93
|
-
with simultaneous data transformation
|
|
92
|
+
- `Commit(value)` – Transforms the input data into a new structure. This enables validation
|
|
93
|
+
with simultaneous data transformation – we'll cover this [later](#transforming-the-validated-object).
|
|
94
94
|
Commited node can't be reopened for validation again, such attempt will trigger runtime error.
|
|
95
95
|
|
|
96
96
|
The distinction between `Dispute` and `Refute` gives you some control over validation flow:
|
|
@@ -99,7 +99,7 @@ enough to halt processing.
|
|
|
99
99
|
|
|
100
100
|
### Validating structured data
|
|
101
101
|
The library's capabilities become more apparent with hierarchical data.
|
|
102
|
-
Pass a path as the first argument to `validate`
|
|
102
|
+
Pass a path as the first argument to `validate` – validator
|
|
103
103
|
will navigate to that value and yield it to the validation block:
|
|
104
104
|
|
|
105
105
|
```ruby rspec validation_hash_aligned
|
|
@@ -125,7 +125,7 @@ expect(result.failure).to match({ children: { bar: { errors: [have_attributes(co
|
|
|
125
125
|
|
|
126
126
|
This separation enables two powerful patterns:
|
|
127
127
|
|
|
128
|
-
**1. Meaningful error keys**
|
|
128
|
+
**1. Meaningful error keys** – Store errors under descriptive names rather than raw data keys:
|
|
129
129
|
|
|
130
130
|
```ruby rspec validation_hash_tuple_unaligned
|
|
131
131
|
result = Validator
|
|
@@ -137,10 +137,10 @@ result = Validator
|
|
|
137
137
|
expect(result.failure).to match({ children: { total: { errors: [have_attributes(code: :excessive)] } } })
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
Note how the `from` parameter accepts an array of paths
|
|
140
|
+
Note how the `from` parameter accepts an array of paths – this creates a tuple from multiple values,
|
|
141
141
|
perfect for cross-field validations.
|
|
142
142
|
|
|
143
|
-
**2. Data transformation**
|
|
143
|
+
**2. Data transformation** – Remap input data into new structures using `Commit` rulings.
|
|
144
144
|
The `from` parameter lets you source data from one location while building transformed
|
|
145
145
|
output at another. We'll explore this pattern
|
|
146
146
|
in detail [later](#transforming-the-validated-object).
|
|
@@ -148,7 +148,7 @@ in detail [later](#transforming-the-validated-object).
|
|
|
148
148
|
### Alternative syntax
|
|
149
149
|
You can also apply rulings directly to validator nodes rather
|
|
150
150
|
of returning them from validation blocks. This permits
|
|
151
|
-
more concise phrasing in certain cases
|
|
151
|
+
more concise phrasing in certain cases – for example when passing validator
|
|
152
152
|
into functions:
|
|
153
153
|
|
|
154
154
|
```ruby rspec node_disputed
|
|
@@ -163,7 +163,7 @@ expect(disputed.to_result.failure)
|
|
|
163
163
|
.to match({ children: { total: { errors: [have_attributes(code: :excessive)] } } })
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
Remember that validators are immutable
|
|
166
|
+
Remember that validators are immutable – methods like `dispute`, `refute`, and `commit`
|
|
167
167
|
return new validator instance with updated state.
|
|
168
168
|
|
|
169
169
|
### Handling missing values
|
|
@@ -171,18 +171,18 @@ The `validate?` method provides flexible handling of missing values.
|
|
|
171
171
|
While `validate` immediately refutes nodes when values aren't found,
|
|
172
172
|
`validate?` offers more nuanced options:
|
|
173
173
|
|
|
174
|
-
**Default behavior:** Skip validation entirely if the value is missing
|
|
174
|
+
**Default behavior:** Skip validation entirely if the value is missing – the validator state
|
|
175
175
|
remains unchanged.
|
|
176
176
|
|
|
177
177
|
**With missing value strategies:** Call `validate?` without a block, then chain `.some_or_nil` or `.option`
|
|
178
178
|
to control how missing values are handled:
|
|
179
179
|
|
|
180
|
-
- **`some_or_nil`**
|
|
180
|
+
- **`some_or_nil`** – Passes `nil` for missing values. In tuples, only missing fields become `nil`,
|
|
181
181
|
not the entire tuple.
|
|
182
|
-
- **`option`**
|
|
182
|
+
- **`option`** – Passes an option type (like `Dry::Result::Failure(Unit)` when using the Dry interface).
|
|
183
183
|
Again, in tuples only missing fields become *none* values.
|
|
184
184
|
|
|
185
|
-
The `option` strategy enables validations where fields have disjunctive relationships
|
|
185
|
+
The `option` strategy enables validations where fields have disjunctive relationships –
|
|
186
186
|
like "either `:foo` or `:bar` must be set, but not both":
|
|
187
187
|
|
|
188
188
|
```ruby rspec validation_option
|
|
@@ -222,7 +222,7 @@ expect(result.failure)
|
|
|
222
222
|
.to match({ children: { foo: { errors: [have_attributes(code: :invalid_access)] } } })
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
-
This means you can validate any object without worrying about method availability
|
|
225
|
+
This means you can validate any object without worrying about method availability – missing methods
|
|
226
226
|
become validation errors rather than runtime exceptions.
|
|
227
227
|
|
|
228
228
|
## Predicates
|
|
@@ -249,11 +249,11 @@ end
|
|
|
249
249
|
```
|
|
250
250
|
|
|
251
251
|
**Key concepts:**
|
|
252
|
-
- **`Ruling::Invalidate`**
|
|
252
|
+
- **`Ruling::Invalidate`** – A suspended ruling that doesn't specify severity (`dispute` vs `refute`).
|
|
253
253
|
The caller determines severity when using the predicate via `satisfy`.
|
|
254
|
-
- **`validate_value`**
|
|
255
|
-
- **`validate_option`**
|
|
256
|
-
This is not required
|
|
254
|
+
- **`validate_value`** – Handles definite values (the common case)
|
|
255
|
+
- **`validate_option`** – Handles optional values from `satisfy?` with the option strategy.
|
|
256
|
+
This is not required – omit if your predicate doesn't need to handle missing values.
|
|
257
257
|
|
|
258
258
|
This separation lets predicates work with both definite and optional values
|
|
259
259
|
while leaving severity decisions to the validation context where they're used.
|
|
@@ -316,7 +316,7 @@ expect(result.failure)
|
|
|
316
316
|
disputes or refutations, giving you control over validation flow.
|
|
317
317
|
|
|
318
318
|
**Missing values:** Like `validate?`, the `satisfy?` method handles missing values
|
|
319
|
-
gracefully
|
|
319
|
+
gracefully – skipping validation by default, or using `some_or_nil`/`option` strategies
|
|
320
320
|
when chained.
|
|
321
321
|
|
|
322
322
|
## Navigation
|
|
@@ -443,10 +443,10 @@ variants (`at?`, `each_at?`) that handle missing values gracefully. Note that
|
|
|
443
443
|
make sense for collection elements.
|
|
444
444
|
|
|
445
445
|
**Supported collections:** Currently `each_at` works with `Array` and `Hash`. You can add support
|
|
446
|
-
for other collection types (like `Set` or `ActiveRecord::Relation`) using [custom wrappers](#custom-wrappers).
|
|
446
|
+
for other collection types (like `Set` or `ActiveRecord::Relation`) using [custom wrappers](#implementing-custom-wrappers).
|
|
447
447
|
|
|
448
448
|
## Flow control
|
|
449
|
-
Basic flow control comes from the `Dispute`/`Refute` distinction
|
|
449
|
+
Basic flow control comes from the `Dispute`/`Refute` distinction – `Refute` rulings skip
|
|
450
450
|
all subsequent validations on that node.
|
|
451
451
|
|
|
452
452
|
For more sophisticated control, use `with_valid` to conditionally execute validation
|
|
@@ -542,12 +542,37 @@ letting you reshape data while validating it.
|
|
|
542
542
|
You can commit values through several mechanisms:
|
|
543
543
|
- Return `Commit(value)` from a `validate` block
|
|
544
544
|
- Call the `commit(value)` method on a validator node
|
|
545
|
+
- Use `transform` / `transform?` - extract and commit values with optional transformation
|
|
545
546
|
- Pass `commit: true` to the `validate` or `satisfy` method (commits the original value if validation passes)
|
|
546
547
|
- Pass `commit: <collection_type>` to the `each_at` - gathers values of all committed nodes
|
|
547
|
-
into the specified collection
|
|
548
|
+
into the specified collection – either `array` or `hash` and commits them to the node
|
|
548
549
|
after the iteration.
|
|
549
550
|
|
|
550
|
-
|
|
551
|
+
The `transform`/`transform?` methods mirror the semantics of the `validate`/`validate?` pair,
|
|
552
|
+
only they don't expect a ruling to be returned from the block, just the bare value.
|
|
553
|
+
The `transform?` variant supports suspended execution with `.option` and `.some_or_nil` strategies.
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
```ruby rspec transformation_hash
|
|
557
|
+
result = Validator.instance({ bar: 'bar' }, coordinator)
|
|
558
|
+
.transform(from: [:bar]) { _1.upcase }
|
|
559
|
+
.to_result
|
|
560
|
+
.value!
|
|
561
|
+
|
|
562
|
+
expect(result).to eq('BAR')
|
|
563
|
+
|
|
564
|
+
result = Validator.instance({}, coordinator)
|
|
565
|
+
.transform?(from: [:bar])
|
|
566
|
+
.option { _1.value_or { 'default' } }
|
|
567
|
+
.to_result
|
|
568
|
+
.value!
|
|
569
|
+
|
|
570
|
+
expect(result).to eq('default')
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Structural commitment
|
|
574
|
+
|
|
575
|
+
Individual value commits aren't enough – you must also commit the containing structure.
|
|
551
576
|
The validator can't automatically determine the desired output format,
|
|
552
577
|
so you need to explicitly commit each level.
|
|
553
578
|
|
|
@@ -558,12 +583,14 @@ def self.item(item)
|
|
|
558
583
|
item
|
|
559
584
|
.satisfy(:name, commit: true) { :presence }
|
|
560
585
|
.satisfy(:unit_price, from: [:price], commit: true ) { :presence }
|
|
586
|
+
.transform(:note, from: [:meta, :note]) { _1 }
|
|
561
587
|
.auto_commit(as: :hash)
|
|
562
588
|
end
|
|
563
589
|
|
|
590
|
+
|
|
564
591
|
original_data = {
|
|
565
592
|
customer: { name: 'John Doe' },
|
|
566
|
-
items: [{ price: 100, name: 'Item 1' }],
|
|
593
|
+
items: [{ price: 100, name: 'Item 1', meta: { note: 'A note' } }],
|
|
567
594
|
price: 100
|
|
568
595
|
}
|
|
569
596
|
|
|
@@ -577,7 +604,7 @@ result = Validator
|
|
|
577
604
|
|
|
578
605
|
transformed_data = {
|
|
579
606
|
customer_name: 'John Doe',
|
|
580
|
-
line_items: [{ name: 'Item 1', unit_price: 100 }],
|
|
607
|
+
line_items: [{ name: 'Item 1', unit_price: 100, note: 'A note' }],
|
|
581
608
|
total: 100
|
|
582
609
|
}
|
|
583
610
|
|
|
@@ -585,13 +612,19 @@ expect(result.success).to eq(transformed_data)
|
|
|
585
612
|
```
|
|
586
613
|
|
|
587
614
|
This example demonstrates the full transformation pipeline:
|
|
588
|
-
1. Extract and validate data from nested sources (`customer.name`)
|
|
615
|
+
1. Extract and validate data from nested sources (`customer.name`, `items.meta.note`)
|
|
589
616
|
2. Commit individual values under new keys (`customer_name`, `total`, `line_items`, `unit_price`)
|
|
590
617
|
3. Build the final transformed structure with `auto_commit`
|
|
591
618
|
|
|
592
619
|
The result is a validated and transformed structure entirely different
|
|
593
620
|
from the original data.
|
|
594
621
|
|
|
622
|
+
**Performance consideration:** This library is built primarily for validation workflows
|
|
623
|
+
with transformation capabilities as a convenience feature.
|
|
624
|
+
It is not a dedicated transformation tool. For transformation-heavy workflows, traditional explicit
|
|
625
|
+
Ruby transformations or specialized tools will provide significantly better performance. Use the
|
|
626
|
+
validator's transformation features when validation is the primary goal and transformation is incidental.
|
|
627
|
+
|
|
595
628
|
## Implementing custom wrappers
|
|
596
629
|
The validator supports `Hash` and `Array` out of the box,
|
|
597
630
|
but you can extend it to work with specialized collection types
|
|
@@ -658,7 +691,7 @@ handling patterns and result types, whether you're using a proprietary solution,
|
|
|
658
691
|
|
|
659
692
|
### Validation errors
|
|
660
693
|
Validation errors must include the `StructuredError` marker module. This module
|
|
661
|
-
defines abstract methods as suggestions rather than requirements
|
|
694
|
+
defines abstract methods as suggestions rather than requirements – the library
|
|
662
695
|
works with any type that includes the module.
|
|
663
696
|
|
|
664
697
|
For simple cases, use the built-in `StructuredError::Record` class, which accepts:
|
|
@@ -694,7 +727,7 @@ method determines how the tree gets transformed into the final error
|
|
|
694
727
|
structure returned by `to_result`. Different applications need different final formats.
|
|
695
728
|
|
|
696
729
|
**Hierarchical Strategy** (`Coordinator::Errors::Hierarchical`)
|
|
697
|
-
Preserves the tree structure as nested hashes
|
|
730
|
+
Preserves the tree structure as nested hashes – most natural for debugging:
|
|
698
731
|
|
|
699
732
|
```ruby rspec with_hierarchical_adapter
|
|
700
733
|
expected_failure = {
|
|
@@ -711,7 +744,7 @@ expect(result.to_result.failure).to eq(expected_failure)
|
|
|
711
744
|
```
|
|
712
745
|
|
|
713
746
|
**Flat Strategy** (`Coordinator::Errors::Flat`)
|
|
714
|
-
Flattens errors into path-value tuples
|
|
747
|
+
Flattens errors into path-value tuples – useful for processing or storage:
|
|
715
748
|
|
|
716
749
|
```ruby rspec with_flat_adapter
|
|
717
750
|
expected_failure = [
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'benchmark'
|
|
4
|
+
require 'active_model'
|
|
5
|
+
require 'dry/validation'
|
|
6
|
+
require 'byebug'
|
|
7
|
+
|
|
8
|
+
require_relative '../../lib/lite/validation/validator'
|
|
9
|
+
require_relative '../../spec/validation/validator/support/functional/coordinators/dry'
|
|
10
|
+
|
|
11
|
+
module Lite
|
|
12
|
+
module Validation
|
|
13
|
+
module Validator
|
|
14
|
+
module Benchmark
|
|
15
|
+
module Comparative
|
|
16
|
+
module Transformation
|
|
17
|
+
INPUT = {
|
|
18
|
+
customer: { name: 'John Doe' },
|
|
19
|
+
items: [
|
|
20
|
+
{ price: '100', name: 'Item 1' },
|
|
21
|
+
{ price: '200', name: 'Item 2' }
|
|
22
|
+
],
|
|
23
|
+
charges: [
|
|
24
|
+
{ price: '50', name: 'Charge 1' }
|
|
25
|
+
],
|
|
26
|
+
item_total: '300',
|
|
27
|
+
charge_total: '50'
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
module HashBench
|
|
31
|
+
def self.call(input)
|
|
32
|
+
{
|
|
33
|
+
customer_name: input.dig(:customer, :name),
|
|
34
|
+
line_items: line_items(input[:items]),
|
|
35
|
+
charges: charges(input[:charges]),
|
|
36
|
+
total: input[:item_total].to_i + input[:charge_total].to_i
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.line_items(items)
|
|
41
|
+
items.map { { unit_price: _1[:price].to_i, name: _1[:name] } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.charges(charges)
|
|
45
|
+
charges.map { { amount: _1[:price].to_i, name: _1[:name] } }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module LiteBench
|
|
50
|
+
def self.call(input)
|
|
51
|
+
Validator
|
|
52
|
+
.instance(input, Support::Functional::Coordinators::Dry::Flat)
|
|
53
|
+
.transform(:customer_name, from: %i[customer name]) { _1 }
|
|
54
|
+
.each_at(:line_items, from: [:items], commit: :array) { line_item(_1) }
|
|
55
|
+
.each_at(:charges, commit: :array) { charge(_1) }
|
|
56
|
+
.transform(:total, from: [%i[item_total charge_total]]) { _1.map(&:to_i).sum }
|
|
57
|
+
.auto_commit(as: :hash)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.line_item(item)
|
|
61
|
+
item
|
|
62
|
+
.transform(:unit_price, from: %i[price]) { _1.to_i }
|
|
63
|
+
.transform(:name) { _1 }
|
|
64
|
+
.auto_commit(as: :hash)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.charge(charge)
|
|
68
|
+
charge
|
|
69
|
+
.transform(:amount, from: %i[price]) { _1.to_i }
|
|
70
|
+
.transform(:name) { _1 }
|
|
71
|
+
.auto_commit(as: :hash)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.run(n) # rubocop:disable Naming/MethodParameterName
|
|
76
|
+
runs = {}
|
|
77
|
+
|
|
78
|
+
runs[:Hash] = proc do
|
|
79
|
+
HashBench.call(INPUT)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
runs[:Lite] = proc do
|
|
83
|
+
LiteBench.call(INPUT).to_result.success
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
runs.to_a.shuffle.each do |key, proc|
|
|
87
|
+
result = ::Benchmark.measure { n.times { proc.call } }
|
|
88
|
+
puts "#{key}: #{result}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
Lite::Validation::Validator::Benchmark::Comparative::Transformation.run(10_000)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'benchmark'
|
|
4
|
+
require 'active_model'
|
|
5
|
+
require 'dry/validation'
|
|
6
|
+
require 'byebug'
|
|
7
|
+
|
|
8
|
+
require_relative '../../spec/validation/validator/support/functional/contracts/hash'
|
|
9
|
+
require_relative '../../spec/validation/validator/support/functional/coordinators/dry'
|
|
10
|
+
require_relative '../../spec/validation/validator/support/shared/predicates/dry'
|
|
11
|
+
|
|
12
|
+
module Lite
|
|
13
|
+
module Validation
|
|
14
|
+
module Validator
|
|
15
|
+
module Benchmark
|
|
16
|
+
module Comparative
|
|
17
|
+
module Validation
|
|
18
|
+
class DryBench < Dry::Validation::Contract
|
|
19
|
+
option(:limit)
|
|
20
|
+
|
|
21
|
+
json do
|
|
22
|
+
required(:id).filled(:string)
|
|
23
|
+
required(:price).value(:integer, gteq?: 0)
|
|
24
|
+
required(:dates).schema do
|
|
25
|
+
required(:issued).value(:date_time)
|
|
26
|
+
required(:due).value(:date_time)
|
|
27
|
+
end
|
|
28
|
+
required(:payments).array do
|
|
29
|
+
schema do
|
|
30
|
+
required(:amount).value(:integer, gteq?: 0)
|
|
31
|
+
required(:type).value(:string, included_in?: %w[card cash])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
required(:items).array do
|
|
35
|
+
schema do
|
|
36
|
+
required(:id).filled(:string)
|
|
37
|
+
required(:name).filled(:string)
|
|
38
|
+
required(:price).value(:integer, gteq?: 0)
|
|
39
|
+
required(:qty).value(:integer, gteq?: 0)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
rule(dates: %i[issued due]) do
|
|
45
|
+
next if value[0] <= value[1]
|
|
46
|
+
|
|
47
|
+
key.failure('first must be less than or equal to the second')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
rule(:price) do
|
|
51
|
+
next if value <= limit
|
|
52
|
+
|
|
53
|
+
key.failure('is excessive')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module ActiveModelBench
|
|
58
|
+
def self.call(data, limit:)
|
|
59
|
+
model ||= BenchModel.instance(data, limit: limit)
|
|
60
|
+
model.valid? ? nil : model.errors
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class BenchModel
|
|
64
|
+
def self.instance(data, limit:)
|
|
65
|
+
dates = Dates.new(**data[:dates])
|
|
66
|
+
items = data[:items].map { Item.new(**data.slice(:id, :name, :price, :qty)) }
|
|
67
|
+
payments = data[:payments].map { Payment.new(**data.slice(:amount, :type)) }
|
|
68
|
+
|
|
69
|
+
BenchModel.new(
|
|
70
|
+
dates: dates,
|
|
71
|
+
items: items,
|
|
72
|
+
payments: payments,
|
|
73
|
+
limit: limit,
|
|
74
|
+
**data.slice(:id, :price)
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class Dates
|
|
79
|
+
include ActiveModel::Model
|
|
80
|
+
include ActiveModel::Attributes
|
|
81
|
+
include ActiveModel::Validations
|
|
82
|
+
|
|
83
|
+
attribute :issued, :datetime
|
|
84
|
+
attribute :due, :datetime
|
|
85
|
+
|
|
86
|
+
validates :issued, presence: true
|
|
87
|
+
validates :due, presence: true
|
|
88
|
+
|
|
89
|
+
validate do
|
|
90
|
+
next if issued < due
|
|
91
|
+
|
|
92
|
+
errors.add('(issued,due)', 'first must be less than or equal to the second')
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class Payment
|
|
97
|
+
include ActiveModel::Model
|
|
98
|
+
include ActiveModel::Attributes
|
|
99
|
+
include ActiveModel::Validations
|
|
100
|
+
|
|
101
|
+
attribute :amount, :integer
|
|
102
|
+
attribute :type, :string
|
|
103
|
+
|
|
104
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
|
105
|
+
validates :type, presence: true, inclusion: %w[cash card]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Item
|
|
109
|
+
include ActiveModel::Model
|
|
110
|
+
include ActiveModel::Attributes
|
|
111
|
+
include ActiveModel::Validations
|
|
112
|
+
|
|
113
|
+
attribute :id, :string
|
|
114
|
+
attribute :name, :string
|
|
115
|
+
attribute :price, :integer
|
|
116
|
+
attribute :qty, :integer
|
|
117
|
+
|
|
118
|
+
validates :price, presence: true, numericality: { greater_than: 0 }
|
|
119
|
+
validates :qty, presence: true, numericality: { greater_than: 0 }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
include ActiveModel::Model
|
|
123
|
+
include ActiveModel::Attributes
|
|
124
|
+
include ActiveModel::Validations
|
|
125
|
+
|
|
126
|
+
attribute :id, :string
|
|
127
|
+
attribute :price, :integer
|
|
128
|
+
attribute :limit, :integer
|
|
129
|
+
attribute :dates
|
|
130
|
+
attribute :items
|
|
131
|
+
attribute :payments
|
|
132
|
+
|
|
133
|
+
validates :id, presence: true
|
|
134
|
+
validates :price, presence: true
|
|
135
|
+
|
|
136
|
+
validate do
|
|
137
|
+
next if dates.valid?
|
|
138
|
+
|
|
139
|
+
dates.errors.messages.each { |attr, msg| errors.add("dates.#{attr}", msg) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
validate do
|
|
143
|
+
items.each_with_index do |item, idx|
|
|
144
|
+
next if item.valid?
|
|
145
|
+
|
|
146
|
+
item.errors.messages.each { |attr, msg| errors.add("items.#{idx}.#{attr}", msg) }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
validate do
|
|
151
|
+
payments.each_with_index do |payment, idx|
|
|
152
|
+
next if payment.valid?
|
|
153
|
+
|
|
154
|
+
payment.errors.messages.each { |attr, msg| errors.add("payments.#{idx}.#{attr}", msg) }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
validate do
|
|
159
|
+
errors.add(:price, 'is excessive') if price > limit
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
LiteBench = Support::Functional::Contracts::Hash
|
|
165
|
+
|
|
166
|
+
VALID = LiteBench::VALID
|
|
167
|
+
INVALID = LiteBench::INVALID
|
|
168
|
+
CONTEXT = LiteBench::CONTEXT
|
|
169
|
+
|
|
170
|
+
def self.run(n) # rubocop:disable Naming/MethodParameterName, Metrics/AbcSize
|
|
171
|
+
runs = {}
|
|
172
|
+
|
|
173
|
+
runs[:ActiveModel] = proc do |idx|
|
|
174
|
+
ActiveModelBench.call(data(idx), **CONTEXT)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
runs[:Dry] = proc do |idx|
|
|
178
|
+
DryBench.new(**CONTEXT).call(data(idx))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
runs[:Lite] = proc do |idx|
|
|
182
|
+
LiteBench.call(
|
|
183
|
+
data(idx),
|
|
184
|
+
Support::Functional::Coordinators::Dry::Flat,
|
|
185
|
+
CONTEXT
|
|
186
|
+
).to_result
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
runs.to_a.shuffle.each do |key, proc|
|
|
190
|
+
result = ::Benchmark.measure { n.times { |idx| proc.call(idx) } }
|
|
191
|
+
puts "#{key}: #{result}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def self.data(idx)
|
|
196
|
+
(idx % 5).zero? ? INVALID : VALID
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
Lite::Validation::Validator::Benchmark::Comparative::Validation.run(1000)
|
|
@@ -6,6 +6,7 @@ require_relative 'implementation/identity'
|
|
|
6
6
|
require_relative 'implementation/iteration'
|
|
7
7
|
require_relative 'implementation/predication'
|
|
8
8
|
require_relative 'implementation/scoping'
|
|
9
|
+
require_relative 'implementation/transformation'
|
|
9
10
|
|
|
10
11
|
module Lite
|
|
11
12
|
module Validation
|
|
@@ -18,6 +19,7 @@ module Lite
|
|
|
18
19
|
include Implementation::Iteration
|
|
19
20
|
include Implementation::Predication
|
|
20
21
|
include Implementation::Scoping
|
|
22
|
+
include Implementation::Transformation
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative '../../../error'
|
|
4
4
|
require_relative 'helpers/with_result'
|
|
5
5
|
|
|
6
6
|
module Lite
|
|
@@ -9,10 +9,24 @@ module Lite
|
|
|
9
9
|
module Node
|
|
10
10
|
module Implementation
|
|
11
11
|
module ApplyRuling
|
|
12
|
-
|
|
12
|
+
def self.apply_ruling(validator, path: nil)
|
|
13
|
+
updated, _meta = validator.result.navigate(*path) do |result|
|
|
14
|
+
applied = yield result
|
|
15
|
+
validator.merge_strategy.transform_result(applied, validator, path)
|
|
16
|
+
end
|
|
17
|
+
Helpers::WithResult.with_result(validator, updated)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.structured_error(coordinator, error, **opts)
|
|
21
|
+
case [error, opts]
|
|
22
|
+
in [StructuredError, {}] then error
|
|
23
|
+
in [Symbol, { ** }] then coordinator.structured_error(error, **opts)
|
|
24
|
+
else raise Error::Fatal, "Unexpected first argument: #{error.inspect}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
13
27
|
|
|
14
|
-
def commit(value)
|
|
15
|
-
ApplyRuling.apply_ruling(self,
|
|
28
|
+
def commit(value, at: nil)
|
|
29
|
+
ApplyRuling.apply_ruling(self, path: at) { _1.commit(value) }
|
|
16
30
|
end
|
|
17
31
|
|
|
18
32
|
def auto_commit(as:)
|
|
@@ -20,21 +34,15 @@ module Lite
|
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
def dispute(error, at: nil, **opts)
|
|
23
|
-
ApplyRuling.apply_ruling(self,
|
|
37
|
+
ApplyRuling.apply_ruling(self, path: at) do |result|
|
|
38
|
+
result.dispute(ApplyRuling.structured_error(coordinator, error, **opts))
|
|
39
|
+
end
|
|
24
40
|
end
|
|
25
41
|
|
|
26
42
|
def refute(error, at: nil, **opts)
|
|
27
|
-
ApplyRuling.apply_ruling(self,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def self.apply_ruling(validator, ruling, path: nil)
|
|
31
|
-
return validator if ruling.is_a?(Ruling::Pass)
|
|
32
|
-
|
|
33
|
-
updated, _meta = validator.result.navigate(*path) do |result|
|
|
34
|
-
applied = Ruling.apply(ruling, result, validator.coordinator)
|
|
35
|
-
validator.merge_strategy.transform_result(applied, validator, path)
|
|
43
|
+
ApplyRuling.apply_ruling(self, path: at) do |result|
|
|
44
|
+
result.refute(ApplyRuling.structured_error(coordinator, error, **opts))
|
|
36
45
|
end
|
|
37
|
-
Helpers::WithResult.with_result(validator, updated)
|
|
38
46
|
end
|
|
39
47
|
end
|
|
40
48
|
end
|
|
@@ -24,8 +24,8 @@ module Lite
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
module Nullify
|
|
27
|
-
def self.child_parameters(
|
|
28
|
-
block.call(option.some_or_nil)
|
|
27
|
+
def self.child_parameters(validator, option, _result, &block)
|
|
28
|
+
block.call(option.some_or_nil, validator.send(:state).value_definite)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def self.block_parameters(_validator, option, _result, &block)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../ruling'
|
|
4
|
+
require_relative '../suspended'
|
|
5
|
+
require_relative 'dig'
|
|
6
|
+
require_relative 'helpers/call_foreign'
|
|
7
|
+
require_relative 'helpers/yield_strategy'
|
|
8
|
+
|
|
9
|
+
module Lite
|
|
10
|
+
module Validation
|
|
11
|
+
module Validator
|
|
12
|
+
module Node
|
|
13
|
+
module Implementation
|
|
14
|
+
module Transformation
|
|
15
|
+
include Ruling::Constructors
|
|
16
|
+
include Dig
|
|
17
|
+
|
|
18
|
+
def transform?(*path, from: nil, &block)
|
|
19
|
+
return Suspended.new(:transform!, self, path, from) if block.nil?
|
|
20
|
+
|
|
21
|
+
transform!(path, from, :skip, block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def transform(*path, from: nil, &block)
|
|
25
|
+
transform!(path, from, :refute, block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def transform!(path, from, strategy, block)
|
|
31
|
+
return self unless result.success?
|
|
32
|
+
|
|
33
|
+
dig(*path, from: from) do |option, result|
|
|
34
|
+
strategy = Helpers::YieldStrategy.to_yield(strategy)
|
|
35
|
+
strategy.block_parameters(self, option, result) do |to_yield|
|
|
36
|
+
Helpers::CallForeign.call_foreign(result, coordinator) do
|
|
37
|
+
value = block.call(to_yield, context)
|
|
38
|
+
committed = result.commit(value)
|
|
39
|
+
merge_strategy.transform_result(committed, self, path)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lite-validation
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tomas Milsimer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lite-data
|
|
@@ -37,7 +37,8 @@ files:
|
|
|
37
37
|
- Gemfile
|
|
38
38
|
- README.md
|
|
39
39
|
- bench/calibrational.rb
|
|
40
|
-
- bench/comparative.rb
|
|
40
|
+
- bench/comparative/transformation.rb
|
|
41
|
+
- bench/comparative/validation.rb
|
|
41
42
|
- bench/functional.rb
|
|
42
43
|
- bench/profile.rb
|
|
43
44
|
- lib/lite/validation.rb
|
|
@@ -86,6 +87,7 @@ files:
|
|
|
86
87
|
- lib/lite/validation/validator/node/implementation/predication.rb
|
|
87
88
|
- lib/lite/validation/validator/node/implementation/scoping.rb
|
|
88
89
|
- lib/lite/validation/validator/node/implementation/scoping/evaluator.rb
|
|
90
|
+
- lib/lite/validation/validator/node/implementation/transformation.rb
|
|
89
91
|
- lib/lite/validation/validator/node/implementation/validation.rb
|
|
90
92
|
- lib/lite/validation/validator/node/implementation/wrap.rb
|
|
91
93
|
- lib/lite/validation/validator/node/root.rb
|
data/bench/comparative.rb
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'benchmark'
|
|
4
|
-
require 'active_model'
|
|
5
|
-
require 'dry/validation'
|
|
6
|
-
require 'byebug'
|
|
7
|
-
|
|
8
|
-
require_relative '../spec/validation/validator/support/functional/contracts/hash'
|
|
9
|
-
require_relative '../spec/validation/validator/support/functional/coordinators/dry'
|
|
10
|
-
require_relative '../spec/validation/validator/support/shared/predicates/dry'
|
|
11
|
-
|
|
12
|
-
module Lite
|
|
13
|
-
module Validation
|
|
14
|
-
module Validator
|
|
15
|
-
module Benchmark
|
|
16
|
-
module Comparative
|
|
17
|
-
class DryBench < Dry::Validation::Contract
|
|
18
|
-
option(:limit)
|
|
19
|
-
|
|
20
|
-
json do
|
|
21
|
-
required(:id).filled(:string)
|
|
22
|
-
required(:price).value(:integer, gteq?: 0)
|
|
23
|
-
required(:dates).schema do
|
|
24
|
-
required(:issued).value(:date_time)
|
|
25
|
-
required(:due).value(:date_time)
|
|
26
|
-
end
|
|
27
|
-
required(:payments).array do
|
|
28
|
-
schema do
|
|
29
|
-
required(:amount).value(:integer, gteq?: 0)
|
|
30
|
-
required(:type).value(:string, included_in?: %w[card cash])
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
required(:items).array do
|
|
34
|
-
schema do
|
|
35
|
-
required(:id).filled(:string)
|
|
36
|
-
required(:name).filled(:string)
|
|
37
|
-
required(:price).value(:integer, gteq?: 0)
|
|
38
|
-
required(:qty).value(:integer, gteq?: 0)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
rule(dates: %i[issued due]) do
|
|
44
|
-
next if value[0] <= value[1]
|
|
45
|
-
|
|
46
|
-
key.failure('first must be less than or equal to the second')
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
rule(:price) do
|
|
50
|
-
next if value <= limit
|
|
51
|
-
|
|
52
|
-
key.failure('is excessive')
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class ActiveModelBench < Dry::Validation::Contract
|
|
57
|
-
def self.call(data, limit:)
|
|
58
|
-
model ||= BenchModel.instance(data, limit: limit)
|
|
59
|
-
model.valid? ? nil : model.errors
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
class BenchModel
|
|
63
|
-
def self.instance(data, limit:)
|
|
64
|
-
dates = Dates.new(**data[:dates])
|
|
65
|
-
items = data[:items].map { Item.new(**data.slice(:id, :name, :price, :qty)) }
|
|
66
|
-
payments = data[:payments].map { Payment.new(**data.slice(:amount, :type)) }
|
|
67
|
-
|
|
68
|
-
BenchModel.new(dates: dates, items: items, payments: payments, limit: limit, **data.slice(:id, :price))
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
class Dates
|
|
72
|
-
include ActiveModel::Model
|
|
73
|
-
include ActiveModel::Attributes
|
|
74
|
-
include ActiveModel::Validations
|
|
75
|
-
|
|
76
|
-
attribute :issued, :datetime
|
|
77
|
-
attribute :due, :datetime
|
|
78
|
-
|
|
79
|
-
validates :issued, presence: true
|
|
80
|
-
validates :due, presence: true
|
|
81
|
-
|
|
82
|
-
validate do
|
|
83
|
-
next if issued < due
|
|
84
|
-
|
|
85
|
-
errors.add('(issued,due)', 'first must be less than or equal to the second')
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class Payment
|
|
90
|
-
include ActiveModel::Model
|
|
91
|
-
include ActiveModel::Attributes
|
|
92
|
-
include ActiveModel::Validations
|
|
93
|
-
|
|
94
|
-
attribute :amount, :integer
|
|
95
|
-
attribute :type, :string
|
|
96
|
-
|
|
97
|
-
validates :amount, presence: true, numericality: { greater_than: 0 }
|
|
98
|
-
validates :type, presence: true, inclusion: %w[cash card]
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
class Item
|
|
102
|
-
include ActiveModel::Model
|
|
103
|
-
include ActiveModel::Attributes
|
|
104
|
-
include ActiveModel::Validations
|
|
105
|
-
|
|
106
|
-
attribute :id, :string
|
|
107
|
-
attribute :name, :string
|
|
108
|
-
attribute :price, :integer
|
|
109
|
-
attribute :qty, :integer
|
|
110
|
-
|
|
111
|
-
validates :price, presence: true, numericality: { greater_than: 0 }
|
|
112
|
-
validates :qty, presence: true, numericality: { greater_than: 0 }
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
include ActiveModel::Model
|
|
116
|
-
include ActiveModel::Attributes
|
|
117
|
-
include ActiveModel::Validations
|
|
118
|
-
|
|
119
|
-
attribute :id, :string
|
|
120
|
-
attribute :price, :integer
|
|
121
|
-
attribute :limit, :integer
|
|
122
|
-
attribute :dates
|
|
123
|
-
attribute :items
|
|
124
|
-
attribute :payments
|
|
125
|
-
|
|
126
|
-
validates :id, presence: true
|
|
127
|
-
validates :price, presence: true
|
|
128
|
-
|
|
129
|
-
validate do
|
|
130
|
-
next if dates.valid?
|
|
131
|
-
|
|
132
|
-
dates.errors.messages.each { |attr, msg| errors.add("dates.#{attr}", msg) }
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
validate do
|
|
136
|
-
items.each_with_index do |item, idx|
|
|
137
|
-
next if item.valid?
|
|
138
|
-
|
|
139
|
-
item.errors.messages.each { |attr, msg| errors.add("items.#{idx}.#{attr}", msg) }
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
validate do
|
|
144
|
-
payments.each_with_index do |payment, idx|
|
|
145
|
-
next if payment.valid?
|
|
146
|
-
|
|
147
|
-
payment.errors.messages.each { |attr, msg| errors.add("payments.#{idx}.#{attr}", msg) }
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
validate do
|
|
152
|
-
errors.add(:price, 'is excessive') if price > limit
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
LiteBench = Support::Functional::Contracts::Hash
|
|
158
|
-
|
|
159
|
-
VALID = LiteBench::VALID
|
|
160
|
-
INVALID = LiteBench::INVALID
|
|
161
|
-
CONTEXT = LiteBench::CONTEXT
|
|
162
|
-
|
|
163
|
-
def self.run(n) # rubocop:disable Naming/MethodParameterName, Metrics/AbcSize
|
|
164
|
-
runs = {}
|
|
165
|
-
|
|
166
|
-
runs[:ActiveModel] = proc do |idx|
|
|
167
|
-
ActiveModelBench.call(data(idx), **CONTEXT)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
runs[:Dry] = proc do |idx|
|
|
171
|
-
DryBench.new(**CONTEXT).call(data(idx))
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
runs[:Lite] = proc do |idx|
|
|
175
|
-
LiteBench.call(
|
|
176
|
-
data(idx),
|
|
177
|
-
Support::Functional::Coordinators::Dry::Flat,
|
|
178
|
-
CONTEXT
|
|
179
|
-
).to_result
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
runs.to_a.shuffle.each do |key, proc|
|
|
183
|
-
result = ::Benchmark.measure { n.times { |idx| proc.call(idx) } }
|
|
184
|
-
puts "#{key}: #{result}"
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def self.data(idx)
|
|
189
|
-
(idx % 5).zero? ? INVALID : VALID
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
Lite::Validation::Validator::Benchmark::Comparative.run(1000)
|