Class: StructuredParams::Params
- Inherits:
-
Object
- Object
- StructuredParams::Params
- Includes:
- ActiveModel::Attributes, ActiveModel::Model, AttributeMethods, I18n, Validations
- Defined in:
- lib/structured_params/params.rb,
sig/structured_params/params.rbs
Overview
Parameter model that supports structured objects and arrays
This class can be used in two ways:
- Strong Parameters validation (API requests)
- Form objects (View integration with form_with/form_for)
Strong Parameters example (API):
class UserParams < StructuredParams::Params
attribute :name, :string
attribute :age, :integer
attribute :address, :object, value_class: AddressParams
attribute :hobbies, :array, value_class: HobbyParams
attribute :tags, :array, value_type: :string
# Validate raw input before type casting (e.g., "12x" for integer fields)
validates_raw :age, format: { with: /\A\d+\z/, message: 'must be numeric string' }
end
# In controller:
user_params = UserParams.new(params)
if user_params.valid?
User.create!(user_params.attributes)
else
render json: { errors: user_params.errors }
end
Form object example (View integration):
class UserRegistrationForm < StructuredParams::Params
attribute :name, :string
attribute :email, :string
validates :name, presence: true
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end
# In controller:
@form = UserRegistrationForm.new(params)
if @form.valid?
User.create!(@form.attributes)
redirect_to user_path
else
render :new
end
# In view:
<%= form_with model: @form, url: users_path do |f| %>
<%= f.text_field :name %>
<%= f.text_field :email %>
<% end %>
rubocop:disable Metrics/ClassLength
Class Method Summary collapse
-
.form_class? ⇒ Boolean
: () -> bool.
-
.model_name ⇒ ::ActiveModel::Name
Override model_name for form helpers By default, removes "Parameters", "Parameter", or "Form" suffix from class name This allows the class to work seamlessly with Rails form helpers.
-
.permit(params, require: true) ⇒ ActionController::Parameters
Permit parameters with optional require.
-
.permit_attribute_names ⇒ Array[untyped]
Generate permitted parameter structure for Strong Parameters : () -> Array.
-
.structured_attributes ⇒ Hash[Symbol, singleton(::StructuredParams::Params)]
Get structured attributes and their classes : () -> Hash[Symbol, singleton(::StructuredParams::Params)].
Instance Method Summary collapse
-
#attribute_keys_present?(params) ⇒ Boolean
: (ActionController::Parameters) -> bool.
-
#attributes(symbolize: false, compact_mode: :none) ⇒ Object
Convert structured objects to Hash and get attributes : (?symbolize: false, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[String, untyped] : (?symbolize: true, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[Symbol, untyped].
-
#errors ⇒ ::StructuredParams::Errors
: () -> ::StructuredParams::Errors.
-
#flat_parameters?(params) ⇒ Boolean
Only treat params as already-flat attributes when none of the top-level values are themselves nested.
-
#format_error_path(attr_name, index = nil) ⇒ String
Format error path using dot notation (always consistent) : (Symbol, Integer?) -> String.
-
#import_structured_errors(structured_errors, prefix) ⇒ void
Integrate structured parameter errors into parent errors : (untyped, String) -> void.
-
#initialize(params) ⇒ Params
constructor
: (Hash[untyped, untyped]|::ActionController::Parameters) -> void.
-
#matches_model_name?(params) ⇒ Boolean
: (ActionController::Parameters) -> bool.
-
#persisted? ⇒ Boolean
Form object support for Rails helpers.
-
#process_action_controller_parameters(params) ⇒ Hash[untyped, untyped]
: (ActionController::Parameters) -> Hash[untyped, untyped].
-
#process_input_parameters(params) ⇒ Hash[untyped, untyped]
Process input parameters : (untyped) -> Hash[untyped, untyped].
-
#require_nested_parameters?(params) ⇒ Boolean
Whether to call params.require(param_key) before permitting.
-
#serialize_structured_value(value, compact_mode: :none) ⇒ Object
Serialize structured values : (untyped, ?compact_mode: :none | :nil_only | :all_blank) -> untyped.
-
#to_key ⇒ nil
: () -> nil.
-
#to_model ⇒ self
: () -> self.
-
#validate_structured_array(attr_name, array_value) ⇒ void
Validate structured arrays : (Symbol, Array) -> void.
-
#validate_structured_object(attr_name, object_value) ⇒ void
Validate structured objects : (Symbol, StructuredParams::Params) -> void.
-
#validate_structured_parameters ⇒ void
Execute structured parameter validation : () -> void.
Methods included from I18n
#attr_segments, #build_nested_label, #human_attribute_name, #resolve_nested_human_attribute_name
Methods included from Validations
Methods included from AttributeMethods
Constructor Details
#initialize(params) ⇒ Params
: (Hash[untyped, untyped]|::ActionController::Parameters) -> void
155 156 157 158 |
# File 'lib/structured_params/params.rb', line 155 def initialize(params) processed_params = process_input_parameters(params) super(**processed_params) end |
Class Method Details
.form_class? ⇒ Boolean
: () -> bool
137 138 139 |
# File 'lib/structured_params/params.rb', line 137 def form_class? name&.end_with?('Form') || false end |
.model_name ⇒ ::ActiveModel::Name
Override model_name for form helpers By default, removes "Parameters", "Parameter", or "Form" suffix from class name This allows the class to work seamlessly with Rails form helpers
Example:
UserRegistrationForm.model_name.name # => "UserRegistration"
UserRegistrationForm.model_name.param_key # => "user_registration"
UserParameters.model_name.name # => "User"
Admin::UserForm.model_name.name # => "Admin::User"
: () -> ::ActiveModel::Name
77 78 79 80 81 82 83 84 |
# File 'lib/structured_params/params.rb', line 77 def model_name @model_name ||= begin namespace = module_parents.detect { |n| n.respond_to?(:use_relative_model_naming?) } # Remove suffix from the full class name (preserving namespace) name_without_suffix = name.sub(/(Parameters?|Form)$/, '') ActiveModel::Name.new(self, namespace, name_without_suffix) end end |
.permit(params, require: true) ⇒ ActionController::Parameters
Permit parameters with optional require
For Form Objects (explicit manual permit):
UserRegistrationForm.permit(params)
# equivalent to:
params.require(:user_registration).permit(*UserRegistrationForm.permit_attribute_names)
For API requests (without require):
UserParams.permit(params, require: false)
# equivalent to:
params.permit(*UserParams.permit_attribute_names)
: (ActionController::Parameters params, ?require: bool) -> ActionController::Parameters
113 114 115 116 117 118 119 120 |
# File 'lib/structured_params/params.rb', line 113 def permit(params, require: true) if require key = model_name.param_key.to_sym params.require(key).permit(*permit_attribute_names) else params.permit(*permit_attribute_names) end end |
.permit_attribute_names ⇒ Array[untyped]
Generate permitted parameter structure for Strong Parameters : () -> Array
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/structured_params/params.rb', line 88 def permit_attribute_names attribute_types.map do |name, type| name = name.to_sym if type.is_a?(Type::Object) || type.is_a?(Type::Array) { name => type.permit_attribute_names } else name end end end |
.structured_attributes ⇒ Hash[Symbol, singleton(::StructuredParams::Params)]
Get structured attributes and their classes : () -> Hash[Symbol, singleton(::StructuredParams::Params)]
124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/structured_params/params.rb', line 124 def structured_attributes @structured_attributes ||= attribute_types.each_with_object({}) do |(name, type), hash| next unless structured_params_type?(type) hash[name] = if type.is_a?(Type::Array) type.item_type.value_class else type.value_class end end end |
Instance Method Details
#attribute_keys_present?(params) ⇒ Boolean
: (ActionController::Parameters) -> bool
268 269 270 |
# File 'lib/structured_params/params.rb', line 268 def attribute_keys_present?(params) params.keys.any? { |key| self.class.attribute_types.key?(key.to_s) } end |
#attributes(symbolize:, compact_mode:) ⇒ Hash[String, untyped] #attributes(symbolize:, compact_mode:) ⇒ Hash[Symbol, untyped]
Convert structured objects to Hash and get attributes : (?symbolize: false, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[String, untyped] : (?symbolize: true, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[Symbol, untyped]
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/structured_params/params.rb', line 184 def attributes(symbolize: false, compact_mode: :none) attrs = super() self.class.structured_attributes.each_key do |name| value = attrs[name.to_s] attrs[name.to_s] = serialize_structured_value(value, compact_mode: compact_mode) end result = symbolize ? attrs.deep_symbolize_keys : attrs case compact_mode when :all_blank result.compact_blank when :nil_only result.compact else result end end |
#errors ⇒ ::StructuredParams::Errors
: () -> ::StructuredParams::Errors
161 162 163 |
# File 'lib/structured_params/params.rb', line 161 def errors @errors ||= Errors.new(self) end |
#flat_parameters?(params) ⇒ Boolean
Only treat params as already-flat attributes when none of the top-level values are themselves nested. A nested value under some other key means the request shape is ambiguous, so we fall through to raising ParameterMissing instead of silently guessing which keys belong here. : (ActionController::Parameters) -> bool
261 262 263 264 265 |
# File 'lib/structured_params/params.rb', line 261 def flat_parameters?(params) return false if params.values.any?(ActionController::Parameters) attribute_keys_present?(params) end |
#format_error_path(attr_name, index = nil) ⇒ String
Format error path using dot notation (always consistent) : (Symbol, Integer?) -> String
310 311 312 313 314 |
# File 'lib/structured_params/params.rb', line 310 def format_error_path(attr_name, index = nil) path_parts = [attr_name] path_parts << index.to_s if index path_parts.join('.') end |
#import_structured_errors(structured_errors, prefix) ⇒ void
This method returns an undefined value.
Integrate structured parameter errors into parent errors : (untyped, String) -> void
340 341 342 343 344 345 346 347 348 |
# File 'lib/structured_params/params.rb', line 340 def import_structured_errors(structured_errors, prefix) structured_errors.each do |error| # Create dotted attribute path and import with the message already resolved # in the child model's i18n context, so the parent model's locale does not # override the child's translations. error_attribute = "#{prefix}.#{error.attribute}" errors.import(error, attribute: error_attribute.to_sym, message: error.) end end |
#matches_model_name?(params) ⇒ Boolean
: (ActionController::Parameters) -> bool
249 250 251 252 253 254 |
# File 'lib/structured_params/params.rb', line 249 def matches_model_name?(params) key = self.class.model_name.param_key return false unless params.key?(key) params[key].is_a?(ActionController::Parameters) end |
#persisted? ⇒ Boolean
Form object support for Rails helpers. : () -> bool
167 168 169 |
# File 'lib/structured_params/params.rb', line 167 def persisted? false end |
#process_action_controller_parameters(params) ⇒ Hash[untyped, untyped]
: (ActionController::Parameters) -> Hash[untyped, untyped]
221 222 223 |
# File 'lib/structured_params/params.rb', line 221 def process_action_controller_parameters(params) self.class.permit(params, require: require_nested_parameters?(params)).to_h end |
#process_input_parameters(params) ⇒ Hash[untyped, untyped]
Process input parameters : (untyped) -> Hash[untyped, untyped]
208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/structured_params/params.rb', line 208 def process_input_parameters(params) case params when ActionController::Parameters process_action_controller_parameters(params) when Hash # ActiveModel::Attributes can handle both symbol and string keys params else raise ArgumentError, "params must be ActionController::Parameters or Hash, got #{params.class}" end end |
#require_nested_parameters?(params) ⇒ Boolean
Whether to call params.require(param_key) before permitting.
Only ever true for Form-suffixed classes (form_class?); non-Form Params/Parameters subclasses always permit the top level directly.
- A value nested under the model's param_key (e.g. params) always wins, even if params were already permitted higher up, since that inner key still needs to be require()'d out.
- Otherwise, params that are already permitted, or whose shape looks flat (see flat_parameters?), are used as-is without requiring.
- Anything else falls through to true, so params.require raises ActionController::ParameterMissing instead of guessing which keys belong to this form. : (ActionController::Parameters) -> bool
239 240 241 242 243 244 245 246 |
# File 'lib/structured_params/params.rb', line 239 def require_nested_parameters?(params) return false unless self.class.form_class? return true if matches_model_name?(params) return false if params.permitted? return false if flat_parameters?(params) true end |
#serialize_structured_value(value, compact_mode: :none) ⇒ Object
Serialize structured values : (untyped, ?compact_mode: :none | :nil_only | :all_blank) -> untyped
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/structured_params/params.rb', line 318 def serialize_structured_value(value, compact_mode: :none) case value when Array result = value.map { |item| item.attributes(symbolize: false, compact_mode: compact_mode) } case compact_mode when :all_blank result.compact_blank when :nil_only result.compact else result end when StructuredParams::Params value.attributes(symbolize: false, compact_mode: compact_mode) else value end end |
#to_key ⇒ nil
: () -> nil
172 173 174 |
# File 'lib/structured_params/params.rb', line 172 def to_key nil end |
#to_model ⇒ self
: () -> self
177 178 179 |
# File 'lib/structured_params/params.rb', line 177 def to_model self end |
#validate_structured_array(attr_name, array_value) ⇒ void
This method returns an undefined value.
Validate structured arrays : (Symbol, Array) -> void
290 291 292 293 294 295 296 297 |
# File 'lib/structured_params/params.rb', line 290 def validate_structured_array(attr_name, array_value) array_value.each_with_index do |item, index| next if item.valid?(validation_context) error_path = format_error_path(attr_name, index) import_structured_errors(item.errors, error_path) end end |
#validate_structured_object(attr_name, object_value) ⇒ void
This method returns an undefined value.
Validate structured objects : (Symbol, StructuredParams::Params) -> void
301 302 303 304 305 306 |
# File 'lib/structured_params/params.rb', line 301 def validate_structured_object(attr_name, object_value) return if object_value.valid?(validation_context) error_path = format_error_path(attr_name, nil) import_structured_errors(object_value.errors, error_path) end |
#validate_structured_parameters ⇒ void
This method returns an undefined value.
Execute structured parameter validation : () -> void
274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/structured_params/params.rb', line 274 def validate_structured_parameters self.class.structured_attributes.each_key do |name| value = attribute(name) next if value.blank? case value when Array validate_structured_array(name, value) else validate_structured_object(name, value) end end end |