3

In my Rails app I want to validate the filter and post_type params.

Both are optional, but if they are present they must have a value and must have a value that matches one in an array of valid values.

In my controller I have two methods for checking them:

def validate_filter
  if params.has_key?(:filter)
    if params[:filter].present?
      if ['popular', 'following', 'picks', 'promoted', 'first-posts'].include?(params[:filter])
        return true
      else
        return false
      end
    else
      return false
    end
  else
    return true
  end
end

def validate_post_type
  if params.has_key?(:post_type)
    if params[:post_type].present?
      if ['discussions', 'snaps', 'code', 'links'].include?(params[:post_type])
        return true
      else
        return false
      end
    else
      return false
    end
  else
    return true
  end
end

And then in my main controller method I do:

def index
    raise ActionController::RoutingError.new('Not Found') unless validate_filter && validate_post_type
    ...

So this means post_type= and post_type=cam would return a 404 but post_type=snaps would return true.

Is there a better way to validate that the params passed are valid but for both empty and if the key itself exists. Using just blank? and present? is not enough in this scenario.

2

4 Answers 4

6

In a case of an API I would consider letting the client know, that there is a validation error rather than just saying 404.

How about using ActiveModel::Validations?

class MyParamsValidator
  include ActiveModel::Validations

  AVAILABLE_FILTERS    = %w(popular following picks promoted first-posts)
  # this might come from an enum like MyModel.post_types
  AVAILABLE_POST_TYPES = %w(discussions snaps code links)

  attr_reader :data

  validates :filter, inclusion: { in: AVAILABLE_FILTERS }, allow_blank: true
  validates :post_type, inclusion: { in: AVAILABLE_POST_TYPES }, allow_blank: true

  def initialize(data)
    @data = data
  end

  def read_attribute_for_validation(key)
    data[key]
  end
end

class MyController < ApplicationController
  before_action :validate_params, only: :index

  def validate_params
    validator = MyParamsValidator.new(params)

    return if validator.valid?

    render json: { errors: validator.errors }, status: 422
  end
end

You can find more info about a nested case with some tests here.

Sign up to request clarification or add additional context in comments.

Comments

5

I would probably move this logic to the model, but if you really want it in the controller you could simplify it.

def validate_filer
  return true unless params.has_key?(:filter)
  ['popular', 'following', 'picks', 'promoted', 'first-posts'].include?(params[:filter])
end

2 Comments

This is nice and minimal. I made it slimmer by doing: %w(popular following picks promoted first-posts).include?(params[:filter]) like @spickermann.
I would not add this to the model at all. I would create a separate object for param validation. Adding to the model is a clear SRP violation.
4

You can do this in before_action callback of the controller.

before_action :validate_params, only: [:index]

def validate_params
  return false unless params[:filter].present? && params[:post_type].present?
   params_include?(:filter, %w(popular following picks promoted first-posts))
   params_include?(:post_type, %w(discussions snaps code links))
end

Comments

2

Perhaps a small helper method:

def validate_filter
  params_include?(:filter, %w(popular following picks promoted first-posts))
end

def validate_filter
  params_include?(:post_type, %w(discussions snaps code links))
end

def params_include?(key, values)
  !params.key?(key) || values.include?(params[key])
end

It is not clear from your question where that params are coming from, if they are query parameters or part of the path. If they are part of the path you might consider using routing constraints in your routes.rb

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.