Let's say you need to handle the following action:
class UsersController < ApplicationController
def update
user = User.find(id)
result = update_user(user, attrs)
if result.success?
render json: result.value!
else
render json: result.errors, status: :unprocessable_entity
end
end
end
A typical approach to input validation would be:
class UsersController < ApplicationController
def update
attrs = params.require(:user).permit(:email, :password, :password_confirmation)
user = User.find(params.require(:id))
result = update_user(user, attrs)
if result.success?
render json: result.value!
else
render json: result.errors, status: :unprocessable_entity
end
end
end
Instead of checking only the existence of the data, we can do more strict validation and verify the type of each parameter:
module Users
class UpdateParamsSchema < ::Dry::Schema::Params
define do
required(:id).filled(:integer)
required(:user).hash do
required(:email).filled(:integer)
required(:password).filled(:string)
required(:password_confirmation).filled(:string)
end
end
end
end
class UsersController < ApplicationController
def update
params = validate_params!(Users::UpdateParamsSchema.new)
user = User.find(params.fetch(:id))
result = update_user(user, params.fetch(:user))
if result.success?
render json: result.value!
else
render json: result.errors, status: :unprocessable_entity
end
end
private
# You can move it to ApplicationController
def validate_params!(schema)
result = schema.call(params.to_unsafe_hash)
if result.success?
result.to_h
else
raise ActionController::BadRequest, 'Invalid HTTP parameters.'
end
end
end
Or, if you want to respond with meaningful error messages:
class UsersController < ApplicationController
def update
with_validated_params(Users::UpdateParamsSchema.new) do |params|
user = User.find(params.fetch(:id))
result = update_user(user, params.fetch(:user))
if result.success?
render json: result.value!
else
render json: result.errors, status: :unprocessable_entity
end
end
end
private
# You can move it to ApplicationController
def with_validated_params(schema)
result = schema.call(params.to_unsafe_hash)
if result.success?
yield result.to_h
else
render json: result.errors.to_h, status: :bad_request
end
end
end
Remember that this is still only validation of the parameters of the incoming request. Any validation related to the business logic of your application should probably be checked at further stages of data processing (for example inside a service object; in this case it would be inside the update_user
method).