Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nsikanikpoh/e3923f49c3ca0aca834386cc02cafdd5 to your computer and use it in GitHub Desktop.
Save nsikanikpoh/e3923f49c3ca0aca834386cc02cafdd5 to your computer and use it in GitHub Desktop.
Ruby on Rails: Validating HTTP parameters

Dry-Schema 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