structured_params 0.1.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0602589143c9592a299fadcdeb5c1464da069e77a0036294ed12e157261efa94'
4
- data.tar.gz: 24d9f4bd999c649da78007696e28e0bf4e3ff5da0b9e77dd24da5d2a9b46062e
3
+ metadata.gz: 6194009fbc5383add9717e12380cea940598248d52e395acf0abaa6045cea93e
4
+ data.tar.gz: 9d5298274672938c883872ef1595d7609d0a13dc8971b395a90efef2c265dbcb
5
5
  SHA512:
6
- metadata.gz: c059ae0f0dd7cbee7996368159bf267025c5d0e76f356de50bf82be556075e69270c1dae007bcaa6bd0d793d7ada93245e83da386b5d4b4b6096a93a08f5916b
7
- data.tar.gz: cf1511eb456ce037fec5d06f0e8f38a041546a43731d368d68a92629b88343793f82105d6b237c2d216142fedb511a9e278a770313cb69cd75542c94de07df68
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.1] - 2025-09-03
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?(StructuredParams::Type::Object) || type.is_a?(StructuredParams::Type::Array)
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 names of StructuredParams attributes (object and array types)
34
- #: () { (String) -> void } -> void
35
- def each_structured_attribute_name
36
- attribute_types.each do |name, type|
37
- yield name if structured_params_type?(type)
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
- #: (untyped) -> bool
54
+ #: (ActiveModel::Type::Value) -> bool
45
55
  def structured_params_type?(type)
46
- type.is_a?(StructuredParams::Type::Object) ||
47
- (type.is_a?(StructuredParams::Type::Array) && type.item_type_is_structured_params_object?)
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: bool) -> Hash[untyped, untyped]
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.each_structured_attribute_name do |name|
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.each_structured_attribute_name do |attr_name|
93
- value = attribute(attr_name)
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(attr_name, value)
114
+ validate_structured_array(name, value)
99
115
  else
100
- validate_structured_object(attr_name, value)
116
+ validate_structured_object(name, value)
101
117
  end
102
118
  end
103
119
  end
104
120
 
105
121
  # Validate structured arrays
106
- #: (String, Array[untyped]) -> void
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
- #: (String, StructuredParams::Params) -> void
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
- #: (String, Integer?) -> String
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module StructuredParams
5
- VERSION = '0.1.4' #: string
5
+ VERSION = '0.2.0' #: string
6
6
  end
@@ -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
- def self.register_types_as(object_name: :object, array_name: :array)
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 nested structures
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, :nested, value_class: AddressParameter
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 names of nested StructuredParams attributes
23
- # : () { (String) -> void } -> void
24
- def self.each_nested_attribute_name: () { (String) -> void } -> void
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 nested parameter type
27
- # : (untyped) -> bool
28
- private def self.structured_params_type?: (untyped) -> bool
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
- # Convert nested objects to Hash and get attributes
34
- # : (symbolize: bool) -> Hash[untyped, untyped]
35
- def attributes: (symbolize: bool) -> Hash[untyped, untyped]
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 nested parameter validation
52
+ # Execute structured parameter validation
44
53
  # : () -> void
45
- def validate_nested_parameters: () -> void
54
+ def validate_structured_parameters: () -> void
46
55
 
47
- # Validate nested arrays
48
- # : (String, Array[untyped]) -> void
49
- def validate_nested_array: (String, Array[untyped]) -> void
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 nested objects
52
- # : (String, StructuredParams::Params) -> void
53
- def validate_nested_object: (String, StructuredParams::Params) -> void
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
- # : (String, Integer?) -> String
57
- def format_error_path: (String, Integer?) -> String
69
+ # : (Symbol, Integer?) -> String
70
+ def format_error_path: (Symbol, Integer?) -> String
58
71
 
59
- # Integrate nested errors into parent errors
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 serialize_nested_value: (untyped) -> untyped
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.1.4
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