Skip to content

Instantly share code, notes, and snippets.

@marekciupak
Last active March 18, 2024 17:53
Show Gist options
  • Save marekciupak/05c86dd0b5e9cd20a859a231364cb466 to your computer and use it in GitHub Desktop.
Save marekciupak/05c86dd0b5e9cd20a859a231364cb466 to your computer and use it in GitHub Desktop.
Ruby on Rails: Validating HTTP parameters

Validating HTTP parameters

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment