I'm using Rails 5 API to build an app for retrieving data. I want to use multiple GET parameters to extract data.
Sample GET Request Format
/users?company=Samsung&position=Engineer
Controller Code for single parameter:-
def index
client = User.where("company = ?", params[:company])
render json: client
end
Controller Code for multiple parameters:-
def index
client = User.where("company = ?", params[:company]).where("position = ?", params[:position])
render json: client
end
Likewise there are many parameters which may/may not be included in the GET request.
What I want
If a user hasn't specified any parameter, he should be shown all the users.
If only a few parameters have been specified, only those parameters should be searched.
What is happening
If a user specifies only position parameter in the GET request, Rails can't handle it & shows a blank json as there's no rule specified for it.
Please note that there are many parameters and it's not possible to write a rule for each and every combination of parameters.
The basic issue with your solution is that it's forcing each where to execute in turn. This means that if the company parameter is nil
you get an empty relation and all where
statements after that will return an empty relation. You need to conditionally filter only if the parameters are provided.
What you're trying to do is best done inside a query/search or filter object (query takes parameters and returns a result set, filter takes a set already generated and filters it down). I like to define these in app/services/
although some people put them in app/queries
.
There are some inconsistencies in your method however, an index endpoint should return a list of objects. Even if you're hoping to get back a single option you should expect that there could be multiple. Let's say we fix up your index endpoint and use a filter object.
# app/controllers/users_controller.rb
def index
users = User.where(1=1)
users = Users::Filter.call(users, params)
render json: users
end
You'll notice I use User.where(1=1)
, this is because I'm use to dealing with rails 3 which returns an array instead of a relation if you use User.all
.
# /app/services/users/filter.rb
class Users::Filter
def self.call(resources, options)
new(users, options).filter
end
private
attr_reader :resources, :options
def initialize(resources, options)
@resources = resources
@options = options
end
def filter
if options[:company]
@resources = resources.where(company: options[:company])
end
if options[:position]
@resources = resources.where(position: options[:position])
end
resources
end
end
The reason I use call
which then invokes the private initialize
and filter
method is so that this class isn't used incorrectly. It has a single point of entry and returns the result set. This also allows me to break out my filtering functions into small bite size chunks that are easy to change.
For this example I did all of the filtering in filter
but you could break these out into methods and even use some clever meta programming to call the filter name if the object responds to it.
EDIT:
You may also consider finding a gem solution like ransack[1] which allows you to pass parameters directly to the object and search it. There are many different types of search/filtering gems out there to meet your needs.
[1] https://github.com/activerecord-hackery/ransack