StructuredParams provides three ways to integrate with Strong Parameters depending on your use case.
- API Requests
- Form Objects
- Manual Control
- Automatic Permit List Generation
- Controller Patterns
- How
permitDetermines the Parameter Key - Choosing the Right Approach
For API endpoints, pass params directly. Only defined attributes are extracted automatically.
class Api::V1::UsersController < ApplicationController
def create
user_params = UserParams.new(params)
if user_params.valid?
user = User.create!(user_params.attributes)
render json: user, status: :created
else
render json: { errors: user_params.errors.to_hash }, status: :unprocessable_entity
end
end
end
# Example request body:
# { "name": "John", "email": "john@example.com", "age": 30 }Note:
StructuredParams::Paramsautomatically extracts only defined attributes from unpermittedActionController::Parameters, providing the same protection as Strong Parameters without explicitpermitcalls.
If you prefer to be explicit, use permit with require: false:
user_params = UserParams.new(UserParams.permit(params, require: false))For web forms, use permit (default require: true). It automatically resolves nested parameter keys such as params[:user_registration].
class UsersController < ApplicationController
def create
@form = UserRegistrationForm.new(UserRegistrationForm.permit(params))
if @form.valid?
user = User.create!(@form.attributes)
redirect_to user, notice: 'User was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
end
# Example form submission:
# params = { user_registration: { name: "John", email: "john@example.com" } }For fine-grained control, use permit_attribute_names directly.
class UsersController < ApplicationController
def create
permitted_params = params.require(:user).permit(*UserParams.permit_attribute_names)
user_params = UserParams.new(permitted_params)
if user_params.valid?
User.create!(user_params.attributes)
else
render json: { errors: user_params.errors }
end
end
end
# UserParams.permit_attribute_names returns:
# [:name, :age, :email, { address: [:street, :city, :postal_code] }, { hobbies: [:name, :level] }]Note: Existing code using
permit_attribute_namescontinues to work without any changes — full backward compatibility is maintained.
permit_attribute_names automatically generates the correct structure for nested objects and arrays.
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
end
UserParams.permit_attribute_names
# => [:name, :age, { address: [:street, :city, :postal_code] }, { hobbies: [:name, :level] }, { tags: [] }]class Api::V1::UsersController < ApplicationController
def create
user_params = UserParams.new(params)
if user_params.valid?
user = User.create!(user_params.attributes)
render json: user, status: :created
else
render json: { errors: user_params.errors.to_hash }, status: :unprocessable_entity
end
end
def update
user = User.find(params[:id])
user_params = UserParams.new(params)
if user_params.valid? && user.update(user_params.attributes)
render json: user
else
render json: { errors: user_params.errors.to_hash }, status: :unprocessable_entity
end
end
endclass UsersController < ApplicationController
def create
@form = UserRegistrationForm.new(UserRegistrationForm.permit(params))
if @form.valid?
user = User.create!(@form.attributes)
redirect_to user, notice: 'User was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
endpermit uses model_name.param_key to determine which key to require:
UserParams.permit(params)
# Internally calls: params.require(:user).permit(...)
UserRegistrationForm.permit(params)
# Internally calls: params.require(:user_registration).permit(...)
Admin::UserForm.permit(params)
# Internally calls: params.require(:admin_user).permit(...)See Form Objects for details on model_name customization.
- ✅ Simple — no boilerplate
- ✅ Automatic filtering — undefined attributes are excluded
- ✅ Same protection — equivalent security to Strong Parameters
user_params = UserParams.new(params)- ✅ Required for form helpers — when using
form_with/form_forin views - ✅ Nested key resolution — automatically extracts from
params[:user_registration]etc. - ✅ Explicit intent — makes Strong Parameters usage clear
# Form object - Required
@form = UserRegistrationForm.new(UserRegistrationForm.permit(params))
# API with explicit permit - Optional but acceptable
user_params = UserParams.new(UserParams.permit(params, require: false))- ✅ Custom permit logic — add extra fields beyond the defined attributes
- ✅ Backward compatibility — drop-in for existing codebases
- ✅ Fine-grained control — integrate with complex Strong Parameters code
permitted = params.require(:user).permit(*UserParams.permit_attribute_names, :custom_field)
user_params = UserParams.new(permitted)