treaty 0.15.0 → 0.16.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 +4 -4
- data/README.md +1 -1
- data/config/locales/en.yml +4 -0
- data/lib/treaty/attribute/option/base.rb +2 -1
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +2 -1
- data/lib/treaty/attribute/option/modifiers/cast_modifier.rb +2 -1
- data/lib/treaty/attribute/option/modifiers/computed_modifier.rb +126 -0
- data/lib/treaty/attribute/option/modifiers/default_modifier.rb +2 -2
- data/lib/treaty/attribute/option/modifiers/transform_modifier.rb +2 -1
- data/lib/treaty/attribute/option/registry_initializer.rb +7 -4
- data/lib/treaty/attribute/option_orchestrator.rb +3 -2
- data/lib/treaty/attribute/validation/attribute_validator.rb +3 -2
- data/lib/treaty/attribute/validation/nested_transformer.rb +31 -22
- data/lib/treaty/attribute/validation/orchestrator/base.rb +3 -2
- data/lib/treaty/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc42fa86dd5dc35d49f0a07a189d9ddc59f619e2341905c1ed62193e0d3069b7
|
|
4
|
+
data.tar.gz: ffc66f03e0ced6e666bb12db50f405117831b48c26475ab034ab4a16f4c431b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b31ad204ddb19df8a296007f334d29e2e220fbb771f9c5a3851309e0a9ec1909dae0dbef3608633f6a5203da0b827fb90c75132e83f5ebb3fcc28f36fd33cc9
|
|
7
|
+
data.tar.gz: 0c6ec71d1be9155da662498264667a63072c5ce513d1ae0987a54091d76aa35a9fd21b3c722db38a8f4154919a2894f442736c44e23ff88961c9a1aadc841c49
|
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
15
|
> [!WARNING]
|
|
16
|
-
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.
|
|
16
|
+
> **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.16.0"`) until the 1.0 release.
|
|
17
17
|
|
|
18
18
|
## 📚 Documentation
|
|
19
19
|
|
data/config/locales/en.yml
CHANGED
|
@@ -56,6 +56,10 @@ en:
|
|
|
56
56
|
invalid_type: "Option 'transform' for attribute '%{attribute}' must be a Proc or Lambda. Got: %{type}"
|
|
57
57
|
execution_error: "Transform failed for attribute '%{attribute}': %{error}"
|
|
58
58
|
|
|
59
|
+
computed:
|
|
60
|
+
invalid_type: "Option 'computed' for attribute '%{attribute}' must be a Proc or Lambda. Got: %{type}"
|
|
61
|
+
execution_error: "Computed failed for attribute '%{attribute}': %{error}"
|
|
62
|
+
|
|
59
63
|
cast:
|
|
60
64
|
invalid_type: "Option 'cast' for attribute '%{attribute}' must be a Symbol. Got: %{type}"
|
|
61
65
|
source_not_supported: "Option 'cast' for attribute '%{attribute}' cannot be used with type '%{source_type}'. Casting is only supported for: %{allowed}"
|
|
@@ -76,8 +76,9 @@ module Treaty
|
|
|
76
76
|
# Override in subclasses if transformation is needed
|
|
77
77
|
#
|
|
78
78
|
# @param value [Object] The value to transform
|
|
79
|
+
# @param _root_data [Hash] Full raw data from root level (used by computed modifier)
|
|
79
80
|
# @return [Object] Transformed value
|
|
80
|
-
def transform_value(value)
|
|
81
|
+
def transform_value(value, _root_data = {})
|
|
81
82
|
value
|
|
82
83
|
end
|
|
83
84
|
|
|
@@ -79,8 +79,9 @@ module Treaty
|
|
|
79
79
|
# The renaming is handled by the orchestrator using target_name
|
|
80
80
|
#
|
|
81
81
|
# @param value [Object] The value to transform
|
|
82
|
+
# @param _root_data [Hash] Unused root data parameter
|
|
82
83
|
# @return [Object] Unchanged value
|
|
83
|
-
def transform_value(value)
|
|
84
|
+
def transform_value(value, _root_data = {})
|
|
84
85
|
value
|
|
85
86
|
end
|
|
86
87
|
end
|
|
@@ -162,8 +162,9 @@ module Treaty
|
|
|
162
162
|
# Skips conversion for nil values (handled by RequiredValidator)
|
|
163
163
|
#
|
|
164
164
|
# @param value [Object] The current value
|
|
165
|
+
# @param _root_data [Hash] Unused root data parameter
|
|
165
166
|
# @return [Object] Converted value
|
|
166
|
-
def transform_value(value) # rubocop:disable Metrics/MethodLength
|
|
167
|
+
def transform_value(value, _root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
167
168
|
return value if value.nil? # Cast doesn't modify nil, required validator handles it.
|
|
168
169
|
|
|
169
170
|
target_type = option_value
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
module Modifiers
|
|
7
|
+
# Computes attribute values from all available raw data.
|
|
8
|
+
#
|
|
9
|
+
# ## Key Difference from Transform
|
|
10
|
+
#
|
|
11
|
+
# - `transform:` receives only `value:` (the current attribute's value)
|
|
12
|
+
# - `computed:` receives `**attributes` (ALL raw data from root level)
|
|
13
|
+
#
|
|
14
|
+
# ## Usage Examples
|
|
15
|
+
#
|
|
16
|
+
# Simple mode:
|
|
17
|
+
# string :full_name, computed: ->(**attrs) {
|
|
18
|
+
# "#{attrs.dig(:user, :first_name)} #{attrs.dig(:user, :last_name)}"
|
|
19
|
+
# }
|
|
20
|
+
#
|
|
21
|
+
# Advanced mode with custom error message:
|
|
22
|
+
# string :full_name, computed: {
|
|
23
|
+
# is: ->(**attrs) { "#{attrs.dig(:user, :first_name)} #{attrs.dig(:user, :last_name)}" },
|
|
24
|
+
# message: "Failed to compute full name"
|
|
25
|
+
# }
|
|
26
|
+
#
|
|
27
|
+
# ## Use Cases
|
|
28
|
+
#
|
|
29
|
+
# 1. **Derived fields (full name from parts)**:
|
|
30
|
+
# ```ruby
|
|
31
|
+
# response 200 do
|
|
32
|
+
# object :user do
|
|
33
|
+
# string :first_name
|
|
34
|
+
# string :last_name
|
|
35
|
+
# string :full_name, computed: ->(**attrs) {
|
|
36
|
+
# "#{attrs.dig(:user, :first_name)} #{attrs.dig(:user, :last_name)}"
|
|
37
|
+
# }
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
# ```
|
|
41
|
+
#
|
|
42
|
+
# 2. **Calculated values (word count)**:
|
|
43
|
+
# ```ruby
|
|
44
|
+
# response 200 do
|
|
45
|
+
# object :post do
|
|
46
|
+
# string :content
|
|
47
|
+
# integer :word_count, computed: ->(**attrs) {
|
|
48
|
+
# attrs.dig(:post, :content).to_s.split.size
|
|
49
|
+
# }
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
# ```
|
|
53
|
+
#
|
|
54
|
+
# 3. **Cross-object computations**:
|
|
55
|
+
# ```ruby
|
|
56
|
+
# response 200 do
|
|
57
|
+
# object :order do
|
|
58
|
+
# integer :quantity
|
|
59
|
+
# integer :unit_price
|
|
60
|
+
# integer :total, computed: ->(**attrs) {
|
|
61
|
+
# attrs.dig(:order, :quantity).to_i * attrs.dig(:order, :unit_price).to_i
|
|
62
|
+
# }
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
# ```
|
|
66
|
+
#
|
|
67
|
+
# ## Important Notes
|
|
68
|
+
#
|
|
69
|
+
# - Lambda must accept `**attributes` (named argument splat)
|
|
70
|
+
# - Receives full raw data from root level (not just current object)
|
|
71
|
+
# - **Always computes** - ignores any existing value, result replaces everything
|
|
72
|
+
# - All exceptions raised in lambda are caught and re-raised as Validation errors
|
|
73
|
+
# - Computation is applied during Phase 3 (transformation phase)
|
|
74
|
+
# - Executes FIRST in modifier chain: computed -> transform -> cast -> default -> as
|
|
75
|
+
#
|
|
76
|
+
# ## Advanced Mode
|
|
77
|
+
#
|
|
78
|
+
# Schema format: `{ is: lambda, message: nil }`
|
|
79
|
+
class ComputedModifier < Treaty::Attribute::Option::Base
|
|
80
|
+
# Validates that computed value is a lambda
|
|
81
|
+
#
|
|
82
|
+
# @raise [Treaty::Exceptions::Validation] If computed is not a Proc/lambda
|
|
83
|
+
# @return [void]
|
|
84
|
+
def validate_schema!
|
|
85
|
+
computed_lambda = option_value
|
|
86
|
+
|
|
87
|
+
return if computed_lambda.respond_to?(:call)
|
|
88
|
+
|
|
89
|
+
raise Treaty::Exceptions::Validation,
|
|
90
|
+
I18n.t(
|
|
91
|
+
"treaty.attributes.modifiers.computed.invalid_type",
|
|
92
|
+
attribute: @attribute_name,
|
|
93
|
+
type: computed_lambda.class
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Computes value using the provided lambda and full root data
|
|
98
|
+
# Always executes - ignores any existing value
|
|
99
|
+
#
|
|
100
|
+
# @param _value [Object] The current value (ignored - always computes)
|
|
101
|
+
# @param root_data [Hash] Full raw data from root level
|
|
102
|
+
# @return [Object] Computed value
|
|
103
|
+
def transform_value(_value, root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
104
|
+
computed_lambda = option_value
|
|
105
|
+
|
|
106
|
+
# Call lambda with full root data as named arguments
|
|
107
|
+
computed_lambda.call(**root_data)
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
attributes = {
|
|
110
|
+
attribute: @attribute_name,
|
|
111
|
+
error: e.message
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Catch all exceptions from lambda execution
|
|
115
|
+
error_message = resolve_custom_message(**attributes) || I18n.t(
|
|
116
|
+
"treaty.attributes.modifiers.computed.execution_error",
|
|
117
|
+
**attributes
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
raise Treaty::Exceptions::Validation, error_message
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -80,9 +80,9 @@ module Treaty
|
|
|
80
80
|
# Empty strings, empty arrays, and false are NOT replaced
|
|
81
81
|
#
|
|
82
82
|
# @param value [Object] The current value
|
|
83
|
-
# @param
|
|
83
|
+
# @param _root_data [Hash] Unused root data parameter
|
|
84
84
|
# @return [Object] Default value if original is nil, otherwise original value
|
|
85
|
-
def transform_value(value,
|
|
85
|
+
def transform_value(value, _root_data = {})
|
|
86
86
|
# Only apply default if value is nil
|
|
87
87
|
# Empty strings, empty arrays, false are NOT replaced
|
|
88
88
|
return value unless value.nil?
|
|
@@ -82,8 +82,9 @@ module Treaty
|
|
|
82
82
|
# Skips transformation for nil values (handled by RequiredValidator)
|
|
83
83
|
#
|
|
84
84
|
# @param value [Object] The current value
|
|
85
|
+
# @param _root_data [Hash] Unused root data parameter
|
|
85
86
|
# @return [Object] Transformed value
|
|
86
|
-
def transform_value(value) # rubocop:disable Metrics/MethodLength
|
|
87
|
+
def transform_value(value, _root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
87
88
|
return value if value.nil? # Transform doesn't modify nil, required validator handles it.
|
|
88
89
|
|
|
89
90
|
transform_lambda = option_value
|
|
@@ -26,10 +26,11 @@ module Treaty
|
|
|
26
26
|
#
|
|
27
27
|
# ## Built-in Modifiers
|
|
28
28
|
#
|
|
29
|
-
# - `:
|
|
30
|
-
# - `:default` → DefaultModifier - Provides default values
|
|
29
|
+
# - `:computed` → ComputedModifier - Computes values from all raw data (executes first)
|
|
31
30
|
# - `:transform` → TransformModifier - Transforms values using custom lambdas
|
|
32
31
|
# - `:cast` → CastModifier - Converts values between types automatically
|
|
32
|
+
# - `:default` → DefaultModifier - Provides default values
|
|
33
|
+
# - `:as` → AsModifier - Renames attributes
|
|
33
34
|
#
|
|
34
35
|
# ## Built-in Conditionals
|
|
35
36
|
#
|
|
@@ -85,13 +86,15 @@ module Treaty
|
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
# Registers all built-in modifiers
|
|
89
|
+
# Order matters: computed runs first, then transform, cast, default, as
|
|
88
90
|
#
|
|
89
91
|
# @return [void]
|
|
90
92
|
def register_modifiers!
|
|
91
|
-
Registry.register(:
|
|
92
|
-
Registry.register(:default, Modifiers::DefaultModifier, category: :modifier)
|
|
93
|
+
Registry.register(:computed, Modifiers::ComputedModifier, category: :modifier)
|
|
93
94
|
Registry.register(:transform, Modifiers::TransformModifier, category: :modifier)
|
|
94
95
|
Registry.register(:cast, Modifiers::CastModifier, category: :modifier)
|
|
96
|
+
Registry.register(:default, Modifiers::DefaultModifier, category: :modifier)
|
|
97
|
+
Registry.register(:as, Modifiers::AsModifier, category: :modifier)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
# Registers all built-in conditionals
|
|
@@ -103,10 +103,11 @@ module Treaty
|
|
|
103
103
|
# Applies transformations like defaults, type coercion, etc.
|
|
104
104
|
#
|
|
105
105
|
# @param value [Object] The value to transform
|
|
106
|
+
# @param root_data [Hash] Full raw data from root level (used by computed modifier)
|
|
106
107
|
# @return [Object] Transformed value
|
|
107
|
-
def transform_value(value)
|
|
108
|
+
def transform_value(value, root_data = {})
|
|
108
109
|
@processors.values.reduce(value) do |current_value, processor|
|
|
109
|
-
processor.transform_value(current_value)
|
|
110
|
+
processor.transform_value(current_value, root_data)
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
|
|
@@ -68,9 +68,10 @@ module Treaty
|
|
|
68
68
|
# Transforms attribute value through all modifiers
|
|
69
69
|
#
|
|
70
70
|
# @param value [Object] The value to transform
|
|
71
|
+
# @param root_data [Hash] Full raw data from root level (used by computed modifier)
|
|
71
72
|
# @return [Object] Transformed value
|
|
72
|
-
def transform_value(value)
|
|
73
|
-
option_orchestrator.transform_value(value)
|
|
73
|
+
def transform_value(value, root_data = {})
|
|
74
|
+
option_orchestrator.transform_value(value, root_data)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
# Checks if attribute name is transformed
|
|
@@ -22,15 +22,16 @@ module Treaty
|
|
|
22
22
|
# Returns original value if nil or not nested
|
|
23
23
|
#
|
|
24
24
|
# @param value [Object] The value to transform
|
|
25
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
25
26
|
# @return [Object] Transformed value
|
|
26
|
-
def transform(value)
|
|
27
|
+
def transform(value, root_data = {})
|
|
27
28
|
return value if value.nil?
|
|
28
29
|
|
|
29
30
|
case attribute.type
|
|
30
31
|
when :object
|
|
31
|
-
transform_object(value)
|
|
32
|
+
transform_object(value, root_data)
|
|
32
33
|
when :array
|
|
33
|
-
transform_array(value)
|
|
34
|
+
transform_array(value, root_data)
|
|
34
35
|
else
|
|
35
36
|
value
|
|
36
37
|
end
|
|
@@ -41,23 +42,25 @@ module Treaty
|
|
|
41
42
|
# Transforms object (hash) value
|
|
42
43
|
#
|
|
43
44
|
# @param value [Hash] The hash to transform
|
|
45
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
44
46
|
# @return [Hash] Transformed hash
|
|
45
|
-
def transform_object(value)
|
|
47
|
+
def transform_object(value, root_data = {})
|
|
46
48
|
return value unless attribute.nested?
|
|
47
49
|
|
|
48
50
|
transformer = ObjectTransformer.new(attribute)
|
|
49
|
-
transformer.transform(value)
|
|
51
|
+
transformer.transform(value, root_data)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
# Transforms array value
|
|
53
55
|
#
|
|
54
56
|
# @param value [Array] The array to transform
|
|
57
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
55
58
|
# @return [Array] Transformed array
|
|
56
|
-
def transform_array(value)
|
|
59
|
+
def transform_array(value, root_data = {})
|
|
57
60
|
return value unless attribute.nested?
|
|
58
61
|
|
|
59
62
|
transformer = ArrayTransformer.new(attribute)
|
|
60
|
-
transformer.transform(value)
|
|
63
|
+
transformer.transform(value, root_data)
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
# Transforms object (hash) with nested attributes
|
|
@@ -74,15 +77,16 @@ module Treaty
|
|
|
74
77
|
# Transforms hash by processing all nested attributes
|
|
75
78
|
#
|
|
76
79
|
# @param value [Hash] The source hash
|
|
80
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
77
81
|
# @return [Hash] Transformed hash with processed attributes
|
|
78
|
-
def transform(value)
|
|
82
|
+
def transform(value, root_data = {})
|
|
79
83
|
transformed = {}
|
|
80
84
|
|
|
81
85
|
attribute.collection_of_attributes.each do |nested_attribute|
|
|
82
86
|
# Check if conditional (if/unless option) - skip attribute if condition evaluates to skip
|
|
83
87
|
next unless should_process_attribute?(nested_attribute, value)
|
|
84
88
|
|
|
85
|
-
process_attribute(nested_attribute, value, transformed)
|
|
89
|
+
process_attribute(nested_attribute, value, transformed, root_data)
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
transformed
|
|
@@ -177,8 +181,9 @@ module Treaty
|
|
|
177
181
|
# @param nested_attribute [Attribute::Base] Attribute to process
|
|
178
182
|
# @param source_hash [Hash] Source data
|
|
179
183
|
# @param target_hash [Hash] Target hash to populate
|
|
184
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
180
185
|
# @return [void]
|
|
181
|
-
def process_attribute(nested_attribute, source_hash, target_hash) # rubocop:disable Metrics/MethodLength
|
|
186
|
+
def process_attribute(nested_attribute, source_hash, target_hash, root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
182
187
|
source_name = nested_attribute.name
|
|
183
188
|
nested_value = source_hash.fetch(source_name, nil)
|
|
184
189
|
|
|
@@ -189,10 +194,10 @@ module Treaty
|
|
|
189
194
|
nested_transformer = NestedTransformer.new(nested_attribute)
|
|
190
195
|
validator.validate_type!(nested_value) unless nested_value.nil?
|
|
191
196
|
validator.validate_required!(nested_value)
|
|
192
|
-
nested_transformer.transform(nested_value)
|
|
197
|
+
nested_transformer.transform(nested_value, root_data)
|
|
193
198
|
else
|
|
194
199
|
validator.validate_value!(nested_value)
|
|
195
|
-
validator.transform_value(nested_value)
|
|
200
|
+
validator.transform_value(nested_value, root_data)
|
|
196
201
|
end
|
|
197
202
|
|
|
198
203
|
target_name = validator.target_name
|
|
@@ -218,13 +223,14 @@ module Treaty
|
|
|
218
223
|
# Handles both simple arrays (:_self) and complex arrays (objects)
|
|
219
224
|
#
|
|
220
225
|
# @param value [Array] The source array
|
|
226
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
221
227
|
# @return [Array] Transformed array
|
|
222
|
-
def transform(value)
|
|
228
|
+
def transform(value, root_data = {})
|
|
223
229
|
value.each_with_index.map do |item, index|
|
|
224
230
|
if simple_array?
|
|
225
|
-
transform_simple_element(item, index)
|
|
231
|
+
transform_simple_element(item, index, root_data)
|
|
226
232
|
else
|
|
227
|
-
transform_array_item(item, index)
|
|
233
|
+
transform_array_item(item, index, root_data)
|
|
228
234
|
end
|
|
229
235
|
end
|
|
230
236
|
end
|
|
@@ -325,16 +331,17 @@ module Treaty
|
|
|
325
331
|
#
|
|
326
332
|
# @param item [Object] Array element to transform
|
|
327
333
|
# @param index [Integer] Element index for error messages
|
|
334
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
328
335
|
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
329
336
|
# @return [Object] Transformed element value
|
|
330
|
-
def transform_simple_element(item, index) # rubocop:disable Metrics/MethodLength
|
|
337
|
+
def transform_simple_element(item, index, root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
331
338
|
self_attribute = attribute.collection_of_attributes.first
|
|
332
339
|
validator = AttributeValidator.new(self_attribute)
|
|
333
340
|
validator.validate_schema!
|
|
334
341
|
|
|
335
342
|
begin
|
|
336
343
|
validator.validate_value!(item)
|
|
337
|
-
validator.transform_value(item)
|
|
344
|
+
validator.transform_value(item, root_data)
|
|
338
345
|
rescue Treaty::Exceptions::Validation => e
|
|
339
346
|
raise Treaty::Exceptions::Validation,
|
|
340
347
|
I18n.t(
|
|
@@ -350,9 +357,10 @@ module Treaty
|
|
|
350
357
|
#
|
|
351
358
|
# @param item [Hash] Array element to transform
|
|
352
359
|
# @param index [Integer] Element index for error messages
|
|
360
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
353
361
|
# @raise [Treaty::Exceptions::Validation] If item is not a Hash
|
|
354
362
|
# @return [Hash] Transformed hash
|
|
355
|
-
def transform_array_item(item, index) # rubocop:disable Metrics/MethodLength
|
|
363
|
+
def transform_array_item(item, index, root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
356
364
|
unless item.is_a?(Hash)
|
|
357
365
|
raise Treaty::Exceptions::Validation,
|
|
358
366
|
I18n.t(
|
|
@@ -369,7 +377,7 @@ module Treaty
|
|
|
369
377
|
# Check if conditional (if/unless option) - skip attribute if condition evaluates to skip
|
|
370
378
|
next unless should_process_attribute?(nested_attribute, item)
|
|
371
379
|
|
|
372
|
-
process_attribute(nested_attribute, item, transformed, index)
|
|
380
|
+
process_attribute(nested_attribute, item, transformed, index, root_data)
|
|
373
381
|
end
|
|
374
382
|
|
|
375
383
|
transformed
|
|
@@ -382,9 +390,10 @@ module Treaty
|
|
|
382
390
|
# @param source_hash [Hash] Source data
|
|
383
391
|
# @param target_hash [Hash] Target hash to populate
|
|
384
392
|
# @param index [Integer] Array index for error messages
|
|
393
|
+
# @param root_data [Hash] Full raw data from root level (for computed modifier)
|
|
385
394
|
# @raise [Treaty::Exceptions::Validation] If validation fails
|
|
386
395
|
# @return [void]
|
|
387
|
-
def process_attribute(nested_attribute, source_hash, target_hash, index) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
396
|
+
def process_attribute(nested_attribute, source_hash, target_hash, index, root_data = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
388
397
|
source_name = nested_attribute.name
|
|
389
398
|
nested_value = source_hash.fetch(source_name, nil)
|
|
390
399
|
|
|
@@ -396,10 +405,10 @@ module Treaty
|
|
|
396
405
|
nested_transformer = NestedTransformer.new(nested_attribute)
|
|
397
406
|
validator.validate_type!(nested_value) unless nested_value.nil?
|
|
398
407
|
validator.validate_required!(nested_value)
|
|
399
|
-
nested_transformer.transform(nested_value)
|
|
408
|
+
nested_transformer.transform(nested_value, root_data)
|
|
400
409
|
else
|
|
401
410
|
validator.validate_value!(nested_value)
|
|
402
|
-
validator.transform_value(nested_value)
|
|
411
|
+
validator.transform_value(nested_value, root_data)
|
|
403
412
|
end
|
|
404
413
|
rescue Treaty::Exceptions::Validation => e
|
|
405
414
|
raise Treaty::Exceptions::Validation,
|
|
@@ -219,7 +219,7 @@ module Treaty
|
|
|
219
219
|
validate_and_transform_nested(attribute, value, validator)
|
|
220
220
|
else
|
|
221
221
|
validator.validate_value!(value)
|
|
222
|
-
validator.transform_value(value)
|
|
222
|
+
validator.transform_value(value, data)
|
|
223
223
|
end
|
|
224
224
|
end
|
|
225
225
|
|
|
@@ -249,8 +249,9 @@ module Treaty
|
|
|
249
249
|
|
|
250
250
|
# Step 4: Transform non-nil value
|
|
251
251
|
# At this point, value is guaranteed to be non-nil
|
|
252
|
+
# Pass full root data as context for computed modifiers
|
|
252
253
|
transformer = NestedTransformer.new(attribute)
|
|
253
|
-
transformer.transform(value)
|
|
254
|
+
transformer.transform(value, data)
|
|
254
255
|
end
|
|
255
256
|
end
|
|
256
257
|
end
|
data/lib/treaty/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: treaty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton Sokolov
|
|
@@ -160,6 +160,7 @@ files:
|
|
|
160
160
|
- lib/treaty/attribute/option/conditionals/unless_conditional.rb
|
|
161
161
|
- lib/treaty/attribute/option/modifiers/as_modifier.rb
|
|
162
162
|
- lib/treaty/attribute/option/modifiers/cast_modifier.rb
|
|
163
|
+
- lib/treaty/attribute/option/modifiers/computed_modifier.rb
|
|
163
164
|
- lib/treaty/attribute/option/modifiers/default_modifier.rb
|
|
164
165
|
- lib/treaty/attribute/option/modifiers/transform_modifier.rb
|
|
165
166
|
- lib/treaty/attribute/option/registry.rb
|