Module: APIHelper::Filterable

Extended by:
ActiveSupport::Concern
Defined in:
lib/api_helper/filterable.rb

Overview

Helper To Make Resource APIs Filterable

A filterable resource API supports requests to filter resources according to specific criteria, using the filter query parameter.

For example, the following is a request for all products that has a particular color:

GET /products?filter[color]=red

With this approach, multiple filters can be applied to a single request:

GET /products?filter[color]=red&filter[status]=in-stock

Multiple filters are applied with the AND condition.

OR conditions of a single value can be represented as:

GET /products?filter[color]=red,blue,yellow

A few functions: not, greater_then, less_then, greater_then_or_equal, less_then_or_equal, between and like can be used while filtering the data, for example:

GET /products?filter[color]=not(red)
GET /products?filter[price]=greater_then(1000)
GET /products?filter[price]=less_then_or_equal(2000)
GET /products?filter[price]=between(1000,2000)
GET /products?filter[name]=like(%lovely%)

Usage

Include this Concern in your Action Controller:

SamplesController < ApplicationController
  include APIHelpers::Filterable
end

or in your Grape API class:

class SampleAPI < Grape::API
  include APIHelper::Filterable
end

then use the filter method like this:

resources :products do
  get do
    @products = filter(Post, filterable_fields: [:name, :price, :color])
    # ...
  end
end

The filter method will return the scoped model, based directly from the requested URL.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.filter_param_desc(for_field: nil) ⇒ Object

Return the ‘fields’ param description



121
122
123
124
125
126
127
# File 'lib/api_helper/filterable.rb', line 121

def self.filter_param_desc(for_field: nil)
  if for_field.present?
    "Filter data base on the '#{for_field}' field."
  else
    "Filter the data."
  end
end

Instance Method Details

#filter(resource, filterable_fields: []) ⇒ Object

Filter resources of a collection from the request parameter

Params:

resource

ActiveRecord::Base or ActiveRecord::Relation resource collection to filter data from

filterable_fields

Array of Symbols fields that are allowed to be filtered, default to all



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/api_helper/filterable.rb', line 72

def filter(resource, filterable_fields: [])
  # parse the request parameter
  if params[:filter].is_a? Hash
    @filter = params[:filter]
    filterable_fields = filterable_fields.map(&:to_s)

    @filter.each_pair do |field, condition|
      next if filterable_fields.present? && !filterable_fields.include?(field)
      field = resource.connection.quote_string(field)

      next if resource.columns_hash[field].blank?
      field_type = resource.columns_hash[field].type

      # if a function is used
      if func = condition.match(/(?<function>[^\(\)]+)\((?<param>.*)\)/)
        case func[:function]
        when 'not'
          values = func[:param].split(',')
          values.map!(&:to_bool) if field_type == :boolean
          resource = resource.where.not(field => values)
        when 'greater_then'
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" > ?", func[:param])
        when 'less_then'
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" < ?", func[:param])
        when 'greater_then_or_equal'
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" >= ?", func[:param])
        when 'less_then_or_equal'
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" <= ?", func[:param])
        when 'between'
          param = func[:param].split(',')
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" BETWEEN ? AND ?", param.first, param.last)
        when 'like'
          resource = resource.where("\"#{resource.table_name}\".\"#{field}\" LIKE ?", func[:param])
        when 'null'
          resource = resource.where(field => nil)
        end
      # if not function
      else
        values = condition.split(',')
        values.map!(&:to_bool) if field_type == :boolean
        resource = resource.where(field => values)
      end
    end
  end

  return resource
end