Reputation: 28853
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.
Upvotes: 3
Views: 28321
Reputation: 422
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
Upvotes: 4
Reputation: 1004
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.
Upvotes: 6
Reputation: 107142
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
Upvotes: 2
Reputation: 988
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
Upvotes: 5