structured_params 0.1.3 → 0.2.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/CHANGELOG.md +5 -1
- data/lib/structured_params/errors.rb +68 -0
- data/lib/structured_params/params.rb +49 -27
- data/lib/structured_params/version.rb +1 -1
- data/lib/structured_params.rb +7 -1
- data/sig/structured_params/errors.rbs +36 -0
- data/sig/structured_params/params.rbs +42 -29
- data/sig/structured_params.rbs +12 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6194009fbc5383add9717e12380cea940598248d52e395acf0abaa6045cea93e
|
4
|
+
data.tar.gz: 9d5298274672938c883872ef1595d7609d0a13dc8971b395a90efef2c265dbcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37c7d0ead65ced25e00fb82d34ebd0b8d6a049ae3ec966a9cb296769fb611c95c0381dbaa8ccdb5aca0c2760296b56a57de6dce030d348d4b88a587fc1cb5b1e
|
7
|
+
data.tar.gz: 266f140f0d76a90003223e8bb52a6092fa3ce49ca3a9c3eee32881a0f053baee4150802176f6418e13a4ee84bdd884dbe239bdd413061f6f34e7b2a0788685a0
|
data/CHANGELOG.md
CHANGED
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [0.1.
|
8
|
+
## [0.1.4] - 2025-09-04
|
9
|
+
|
10
|
+
**Full Changelog**: https://github.com/Syati/structured_params/compare/v0.1.3...v0.1.4
|
11
|
+
|
12
|
+
|
9
13
|
|
10
14
|
### Fixed
|
11
15
|
- Fixed error key consistency issue where `errors.import` was using string keys instead of symbol keys
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# rbs_inline: enabled
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
5
|
+
module StructuredParams
|
6
|
+
# Custom errors collection that handles nested attribute names
|
7
|
+
class Errors < ActiveModel::Errors
|
8
|
+
# Override to_hash to maintain compatibility with ActiveModel::Errors by default
|
9
|
+
# Add structured option to get nested structure for dot-notation attributes
|
10
|
+
#: (?bool, ?structured: false) -> Hash[Symbol, String]
|
11
|
+
#: (?bool, structured: bool) -> Hash[Symbol, untyped]
|
12
|
+
def to_hash(full_messages = false, structured: false)
|
13
|
+
if structured
|
14
|
+
attribute_messages_hash = build_attribute_messages_hash(full_messages)
|
15
|
+
build_nested_hash({}, attribute_messages_hash)
|
16
|
+
else
|
17
|
+
# Use default ActiveModel::Errors behavior
|
18
|
+
super(full_messages)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override as_json to support structured option
|
23
|
+
# This maintains compatibility with ActiveModel::Errors while adding structured functionality
|
24
|
+
#: (?{ full_messages?: bool, structured?: bool }?) -> Hash[Symbol, untyped]
|
25
|
+
def as_json(options = nil)
|
26
|
+
options ||= {}
|
27
|
+
to_hash(options.fetch(:full_messages, false),
|
28
|
+
structured: options.fetch(:structured, false))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Override messages to support structured option
|
32
|
+
# This maintains compatibility with ActiveModel::Errors while adding structured functionality
|
33
|
+
#: (?structured: bool) -> Hash[Symbol, untyped]
|
34
|
+
def messages(structured: false)
|
35
|
+
hash = to_hash(false, structured: structured)
|
36
|
+
hash.default = [].freeze
|
37
|
+
hash.freeze
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Build a hash with attribute names as keys and their error messages as values
|
44
|
+
# This is used for to_hash(structured: true)
|
45
|
+
#: (bool) -> Hash[Symbol, Array[String]]
|
46
|
+
def build_attribute_messages_hash(full_messages = false)
|
47
|
+
message_method = full_messages ? :full_message : :message
|
48
|
+
|
49
|
+
group_by_attribute.transform_values do |error_list|
|
50
|
+
error_list.map(&message_method)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Build a nested hash structure from flat dot-notation keys
|
55
|
+
# Converts "address.postal_code" to {address: {postal_code: value}}
|
56
|
+
#: (Hash[untyped, untyped], Hash[Symbol, Array[String]], ?String) -> Hash[Symbol, untyped]
|
57
|
+
def build_nested_hash(target_hash, flat_hash, separator = '.')
|
58
|
+
flat_hash.each_with_object(target_hash) do |(key, value), result|
|
59
|
+
*prefix, last = key.to_s.split(separator)
|
60
|
+
# Navigate/create nested structure and use symbols for keys
|
61
|
+
prefix.reduce(result) do |hash, k|
|
62
|
+
hash[k.to_sym] ||= {}
|
63
|
+
end[last.to_sym] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
@@ -15,14 +15,18 @@ module StructuredParams
|
|
15
15
|
include ActiveModel::Model
|
16
16
|
include ActiveModel::Attributes
|
17
17
|
|
18
|
+
# @rbs @errors: ::StructuredParams::Errors?
|
19
|
+
|
18
20
|
class << self
|
21
|
+
# @rbs self.@structured_attributes: Hash[Symbol, singleton(::StructuredParams::Params)]?
|
22
|
+
|
19
23
|
# Generate permitted parameter structure for Strong Parameters
|
20
24
|
#: () -> Array[untyped]
|
21
25
|
def permit_attribute_names
|
22
26
|
attribute_types.map do |name, type|
|
23
27
|
name = name.to_sym
|
24
28
|
|
25
|
-
if type.is_a?(
|
29
|
+
if type.is_a?(Type::Object) || type.is_a?(Type::Array)
|
26
30
|
{ name => type.permit_attribute_names }
|
27
31
|
else
|
28
32
|
name
|
@@ -30,39 +34,51 @@ module StructuredParams
|
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
# Get
|
34
|
-
#: ()
|
35
|
-
def
|
36
|
-
attribute_types.
|
37
|
-
|
37
|
+
# Get structured attributes and their classes
|
38
|
+
#: () -> Hash[Symbol, singleton(::StructuredParams::Params)]
|
39
|
+
def structured_attributes
|
40
|
+
@structured_attributes ||= attribute_types.each_with_object({}) do |(name, type), hash|
|
41
|
+
next unless structured_params_type?(type)
|
42
|
+
|
43
|
+
hash[name] = if type.is_a?(Type::Array)
|
44
|
+
type.item_type.value_class
|
45
|
+
else
|
46
|
+
type.value_class
|
47
|
+
end
|
38
48
|
end
|
39
49
|
end
|
40
50
|
|
41
51
|
private
|
42
52
|
|
43
53
|
# Determine if the specified type is a StructuredParams type
|
44
|
-
#: (
|
54
|
+
#: (ActiveModel::Type::Value) -> bool
|
45
55
|
def structured_params_type?(type)
|
46
|
-
type.is_a?(
|
47
|
-
(type.is_a?(
|
56
|
+
type.is_a?(Type::Object) ||
|
57
|
+
(type.is_a?(Type::Array) && type.item_type_is_structured_params_object?)
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
51
61
|
# Integrate validation of structured objects
|
52
62
|
validate :validate_structured_parameters
|
53
63
|
|
54
|
-
#: (untyped) -> void
|
64
|
+
#: (Hash[untyped, untyped]|::ActionController::Parameters) -> void
|
55
65
|
def initialize(params)
|
56
66
|
processed_params = process_input_parameters(params)
|
57
67
|
super(**processed_params)
|
58
68
|
end
|
59
69
|
|
70
|
+
#: () -> ::StructuredParams::Errors
|
71
|
+
def errors
|
72
|
+
@errors ||= Errors.new(self)
|
73
|
+
end
|
74
|
+
|
60
75
|
# Convert structured objects to Hash and get attributes
|
61
|
-
#: (symbolize:
|
76
|
+
#: (symbolize: true) -> Hash[Symbol, untyped]
|
77
|
+
#: (symbolize: false) -> Hash[String, untyped]
|
62
78
|
def attributes(symbolize: false)
|
63
79
|
attrs = super()
|
64
80
|
|
65
|
-
self.class.
|
81
|
+
self.class.structured_attributes.each_key do |name|
|
66
82
|
value = attrs[name.to_s]
|
67
83
|
attrs[name.to_s] = serialize_structured_value(value)
|
68
84
|
end
|
@@ -89,21 +105,23 @@ module StructuredParams
|
|
89
105
|
# Execute structured parameter validation
|
90
106
|
#: () -> void
|
91
107
|
def validate_structured_parameters
|
92
|
-
self.class.
|
93
|
-
value = attribute(
|
108
|
+
self.class.structured_attributes.each_key do |name|
|
109
|
+
value = attribute(name)
|
94
110
|
next if value.blank?
|
95
111
|
|
96
112
|
case value
|
97
113
|
when Array
|
98
|
-
validate_structured_array(
|
114
|
+
validate_structured_array(name, value)
|
99
115
|
else
|
100
|
-
validate_structured_object(
|
116
|
+
validate_structured_object(name, value)
|
101
117
|
end
|
102
118
|
end
|
103
119
|
end
|
104
120
|
|
105
121
|
# Validate structured arrays
|
106
|
-
|
122
|
+
# @rbs attr_name: Symbol
|
123
|
+
# @rbs array_value: Array[untyped]
|
124
|
+
# @rbs return: void
|
107
125
|
def validate_structured_array(attr_name, array_value)
|
108
126
|
array_value.each_with_index do |item, index|
|
109
127
|
next if item.valid?(validation_context)
|
@@ -114,7 +132,9 @@ module StructuredParams
|
|
114
132
|
end
|
115
133
|
|
116
134
|
# Validate structured objects
|
117
|
-
|
135
|
+
# @rbs attr_name: Symbol
|
136
|
+
# @rbs object_value: ::StructuredParams::Params
|
137
|
+
# @rbs return: void
|
118
138
|
def validate_structured_object(attr_name, object_value)
|
119
139
|
return if object_value.valid?(validation_context)
|
120
140
|
|
@@ -123,21 +143,13 @@ module StructuredParams
|
|
123
143
|
end
|
124
144
|
|
125
145
|
# Format error path using dot notation (always consistent)
|
126
|
-
#: (
|
146
|
+
#: (Symbol, Integer?) -> String
|
127
147
|
def format_error_path(attr_name, index = nil)
|
128
148
|
path_parts = [attr_name]
|
129
149
|
path_parts << index.to_s if index
|
130
150
|
path_parts.join('.')
|
131
151
|
end
|
132
152
|
|
133
|
-
# Integrate structured parameter errors into parent errors
|
134
|
-
#: (untyped, String) -> void
|
135
|
-
def import_structured_errors(structured_errors, prefix)
|
136
|
-
structured_errors.each do |error|
|
137
|
-
errors.import(error, attribute: :"#{prefix}.#{error.attribute}")
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
153
|
# Serialize structured values
|
142
154
|
#: (untyped) -> untyped
|
143
155
|
def serialize_structured_value(value)
|
@@ -150,5 +162,15 @@ module StructuredParams
|
|
150
162
|
value
|
151
163
|
end
|
152
164
|
end
|
165
|
+
|
166
|
+
# Integrate structured parameter errors into parent errors
|
167
|
+
#: (untyped, String) -> void
|
168
|
+
def import_structured_errors(structured_errors, prefix)
|
169
|
+
structured_errors.each do |error|
|
170
|
+
# Create dotted attribute path and import normally
|
171
|
+
error_attribute = "#{prefix}.#{error.attribute}"
|
172
|
+
errors.import(error, attribute: error_attribute.to_sym)
|
173
|
+
end
|
174
|
+
end
|
153
175
|
end
|
154
176
|
end
|
data/lib/structured_params.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'active_model'
|
@@ -7,6 +8,9 @@ require 'action_controller/metal/strong_parameters'
|
|
7
8
|
# version
|
8
9
|
require_relative 'structured_params/version'
|
9
10
|
|
11
|
+
# errors
|
12
|
+
require_relative 'structured_params/errors'
|
13
|
+
|
10
14
|
# types (load first for module definition)
|
11
15
|
require_relative 'structured_params/type/object'
|
12
16
|
require_relative 'structured_params/type/array'
|
@@ -17,13 +21,15 @@ require_relative 'structured_params/params'
|
|
17
21
|
# Main module
|
18
22
|
module StructuredParams
|
19
23
|
# Helper method to register types
|
24
|
+
#: () -> void
|
20
25
|
def self.register_types
|
21
26
|
ActiveModel::Type.register(:object, StructuredParams::Type::Object)
|
22
27
|
ActiveModel::Type.register(:array, StructuredParams::Type::Array)
|
23
28
|
end
|
24
29
|
|
25
30
|
# Helper method to register types with custom names
|
26
|
-
|
31
|
+
#: (object_name: Symbol, array_name: Symbol) -> void
|
32
|
+
def self.register_types_as(object_name:, array_name:)
|
27
33
|
ActiveModel::Type.register(object_name, StructuredParams::Type::Object)
|
28
34
|
ActiveModel::Type.register(array_name, StructuredParams::Type::Array)
|
29
35
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Generated from lib/structured_params/errors.rb with RBS::Inline
|
2
|
+
|
3
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
4
|
+
module StructuredParams
|
5
|
+
# Custom errors collection that handles nested attribute names
|
6
|
+
class Errors < ActiveModel::Errors
|
7
|
+
# Override to_hash to maintain compatibility with ActiveModel::Errors by default
|
8
|
+
# Add structured option to get nested structure for dot-notation attributes
|
9
|
+
# : (?bool, ?structured: false) -> Hash[Symbol, String]
|
10
|
+
# : (?bool, structured: bool) -> Hash[Symbol, untyped]
|
11
|
+
def to_hash: (?bool, ?structured: false) -> Hash[Symbol, String]
|
12
|
+
| (?bool, structured: bool) -> Hash[Symbol, untyped]
|
13
|
+
|
14
|
+
# Override as_json to support structured option
|
15
|
+
# This maintains compatibility with ActiveModel::Errors while adding structured functionality
|
16
|
+
# : (?{ full_messages?: bool, structured?: bool }?) -> Hash[Symbol, untyped]
|
17
|
+
def as_json: (?{ :full_messages? => bool, :structured? => bool }?) -> Hash[Symbol, untyped]
|
18
|
+
|
19
|
+
# Override messages to support structured option
|
20
|
+
# This maintains compatibility with ActiveModel::Errors while adding structured functionality
|
21
|
+
# : (?structured: bool) -> Hash[Symbol, untyped]
|
22
|
+
def messages: (?structured: bool) -> Hash[Symbol, untyped]
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Build a hash with attribute names as keys and their error messages as values
|
27
|
+
# This is used for to_hash(structured: true)
|
28
|
+
# : (bool) -> Hash[Symbol, Array[String]]
|
29
|
+
def build_attribute_messages_hash: (bool) -> Hash[Symbol, Array[String]]
|
30
|
+
|
31
|
+
# Build a nested hash structure from flat dot-notation keys
|
32
|
+
# Converts "address.postal_code" to {address: {postal_code: value}}
|
33
|
+
# : (Hash[untyped, untyped], Hash[Symbol, Array[String]], ?String) -> Hash[Symbol, untyped]
|
34
|
+
def build_nested_hash: (Hash[untyped, untyped], Hash[Symbol, Array[String]], ?String) -> Hash[Symbol, untyped]
|
35
|
+
end
|
36
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# Generated from lib/structured_params/params.rb with RBS::Inline
|
2
2
|
|
3
3
|
module StructuredParams
|
4
|
-
# Parameter model that supports
|
4
|
+
# Parameter model that supports structured objects and arrays
|
5
5
|
#
|
6
6
|
# Usage example:
|
7
7
|
# class UserParameter < StructuredParams::Params
|
8
8
|
# attribute :name, :string
|
9
|
-
# attribute :address, :
|
9
|
+
# attribute :address, :object, value_class: AddressParameter
|
10
10
|
# attribute :hobbies, :array, value_class: HobbyParameter
|
11
11
|
# attribute :tags, :array, value_type: :string
|
12
12
|
# end
|
@@ -15,24 +15,33 @@ module StructuredParams
|
|
15
15
|
|
16
16
|
include ActiveModel::Attributes
|
17
17
|
|
18
|
+
@errors: ::StructuredParams::Errors?
|
19
|
+
|
20
|
+
self.@structured_attributes: Hash[Symbol, singleton(::StructuredParams::Params)]?
|
21
|
+
|
18
22
|
# Generate permitted parameter structure for Strong Parameters
|
19
23
|
# : () -> Array[untyped]
|
20
24
|
def self.permit_attribute_names: () -> Array[untyped]
|
21
25
|
|
22
|
-
# Get
|
23
|
-
# : ()
|
24
|
-
def self.
|
26
|
+
# Get structured attributes and their classes
|
27
|
+
# : () -> Hash[Symbol, singleton(::StructuredParams::Params)]
|
28
|
+
def self.structured_attributes: () -> Hash[Symbol, singleton(::StructuredParams::Params)]
|
25
29
|
|
26
|
-
# Determine if the specified type is a
|
27
|
-
# : (
|
28
|
-
private def self.structured_params_type?: (
|
30
|
+
# Determine if the specified type is a StructuredParams type
|
31
|
+
# : (ActiveModel::Type::Value) -> bool
|
32
|
+
private def self.structured_params_type?: (ActiveModel::Type::Value) -> bool
|
29
33
|
|
30
|
-
# : (untyped) -> void
|
31
|
-
def initialize: (untyped) -> void
|
34
|
+
# : (Hash[untyped, untyped]|::ActionController::Parameters) -> void
|
35
|
+
def initialize: (Hash[untyped, untyped] | ::ActionController::Parameters) -> void
|
32
36
|
|
33
|
-
#
|
34
|
-
|
35
|
-
|
37
|
+
# : () -> ::StructuredParams::Errors
|
38
|
+
def errors: () -> ::StructuredParams::Errors
|
39
|
+
|
40
|
+
# Convert structured objects to Hash and get attributes
|
41
|
+
# : (symbolize: true) -> Hash[Symbol, untyped]
|
42
|
+
# : (symbolize: false) -> Hash[String, untyped]
|
43
|
+
def attributes: (symbolize: true) -> Hash[Symbol, untyped]
|
44
|
+
| (symbolize: false) -> Hash[String, untyped]
|
36
45
|
|
37
46
|
private
|
38
47
|
|
@@ -40,28 +49,32 @@ module StructuredParams
|
|
40
49
|
# : (untyped) -> Hash[untyped, untyped]
|
41
50
|
def process_input_parameters: (untyped) -> Hash[untyped, untyped]
|
42
51
|
|
43
|
-
# Execute
|
52
|
+
# Execute structured parameter validation
|
44
53
|
# : () -> void
|
45
|
-
def
|
54
|
+
def validate_structured_parameters: () -> void
|
46
55
|
|
47
|
-
# Validate
|
48
|
-
# :
|
49
|
-
|
56
|
+
# Validate structured arrays
|
57
|
+
# @rbs attr_name: Symbol
|
58
|
+
# @rbs array_value: Array[untyped]
|
59
|
+
# @rbs return: void
|
60
|
+
def validate_structured_array: (Symbol attr_name, Array[untyped] array_value) -> void
|
50
61
|
|
51
|
-
# Validate
|
52
|
-
# :
|
53
|
-
|
62
|
+
# Validate structured objects
|
63
|
+
# @rbs attr_name: Symbol
|
64
|
+
# @rbs object_value: ::StructuredParams::Params
|
65
|
+
# @rbs return: void
|
66
|
+
def validate_structured_object: (Symbol attr_name, ::StructuredParams::Params object_value) -> void
|
54
67
|
|
55
68
|
# Format error path using dot notation (always consistent)
|
56
|
-
# : (
|
57
|
-
def format_error_path: (
|
69
|
+
# : (Symbol, Integer?) -> String
|
70
|
+
def format_error_path: (Symbol, Integer?) -> String
|
58
71
|
|
59
|
-
#
|
60
|
-
# : (untyped, String) -> void
|
61
|
-
def import_nested_errors: (untyped, String) -> void
|
62
|
-
|
63
|
-
# Serialize nested values
|
72
|
+
# Serialize structured values
|
64
73
|
# : (untyped) -> untyped
|
65
|
-
def
|
74
|
+
def serialize_structured_value: (untyped) -> untyped
|
75
|
+
|
76
|
+
# Integrate structured parameter errors into parent errors
|
77
|
+
# : (untyped, String) -> void
|
78
|
+
def import_structured_errors: (untyped, String) -> void
|
66
79
|
end
|
67
80
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Generated from lib/structured_params.rb with RBS::Inline
|
2
|
+
|
3
|
+
# Main module
|
4
|
+
module StructuredParams
|
5
|
+
# Helper method to register types
|
6
|
+
# : () -> void
|
7
|
+
def self.register_types: () -> void
|
8
|
+
|
9
|
+
# Helper method to register types with custom names
|
10
|
+
# : (object_name: Symbol, array_name: Symbol) -> void
|
11
|
+
def self.register_types_as: (object_name: Symbol, array_name: Symbol) -> void
|
12
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: structured_params
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mizuki Yamamoto
|
@@ -47,10 +47,13 @@ files:
|
|
47
47
|
- CHANGELOG.md
|
48
48
|
- LICENSE.txt
|
49
49
|
- lib/structured_params.rb
|
50
|
+
- lib/structured_params/errors.rb
|
50
51
|
- lib/structured_params/params.rb
|
51
52
|
- lib/structured_params/type/array.rb
|
52
53
|
- lib/structured_params/type/object.rb
|
53
54
|
- lib/structured_params/version.rb
|
55
|
+
- sig/structured_params.rbs
|
56
|
+
- sig/structured_params/errors.rbs
|
54
57
|
- sig/structured_params/params.rbs
|
55
58
|
- sig/structured_params/type/array.rbs
|
56
59
|
- sig/structured_params/type/object.rbs
|