module FetcheableOnApi::Filterable

Filterable implements `filter` parameter support.

Constants

PREDICATES_WITH_ARRAY

Predicates supported for filtering.

Public Class Methods

included(base) click to toggle source

Public class methods

# File lib/fetcheable_on_api/filterable.rb, line 35
def self.included(base)
  base.class_eval do
    extend ClassMethods
    class_attribute :filters_configuration, instance_writer: false
    self.filters_configuration = {}
  end
end

Protected Instance Methods

apply_filters(collection) click to toggle source
# File lib/fetcheable_on_api/filterable.rb, line 101
def apply_filters(collection)
  return collection if params[:filter].blank?

  foa_valid_parameters!(:filter)

  filter_params = params.require(:filter)
                        .permit(valid_keys)
                        .to_hash

  filtering = filter_params.map do |column, values|
    config = filters_configuration[column.to_sym]

    format = config.fetch(:format, :string)
    column_name = config.fetch(:as, column)
    klass = config.fetch(:class_name, collection.klass)
    collection_klass = collection.name.constantize
    association_class_or_name = config.fetch(
      :association, klass.table_name.to_sym
    )

    predicate = config.fetch(:with, :ilike)

    if collection_klass != klass
      collection = collection.joins(association_class_or_name)
    end

    if %i[between not_between].include?(predicate)
      if values.is_a?(String)
        predicates(predicate, collection, klass, column_name, values.split(","))
      else
        values.map do |value|
          predicates(predicate, collection, klass, column_name, value.split(","))
        end.inject(:or)
      end
    elsif values.is_a?(String)
      values.split(",").map do |value|
        predicates(predicate, collection, klass, column_name, value)
      end.inject(:or)
    else
      values.map! { |el| el.split(",") }
      predicates(predicate, collection, klass, column_name, values)
    end
  end

  collection.where(filtering.flatten.compact.inject(:and))
end
foa_default_permitted_types() click to toggle source

Types allowed by default for filter action.

# File lib/fetcheable_on_api/filterable.rb, line 240
def foa_default_permitted_types
  [ActionController::Parameters, Hash, Array]
end
predicates(predicate, collection, klass, column_name, value) click to toggle source

Apply arel predicate on collection

# File lib/fetcheable_on_api/filterable.rb, line 149
def predicates(predicate, collection, klass, column_name, value)
  case predicate
  when :between
    klass.arel_table[column_name].between(value.first..value.last)
  when :does_not_match
    klass.arel_table[column_name].does_not_match("%#{value}%")
  when :does_not_match_all
    klass.arel_table[column_name].does_not_match_all(value)
  when :does_not_match_any
    klass.arel_table[column_name].does_not_match_any(value)
  when :eq
    klass.arel_table[column_name].eq(value)
  when :eq_all
    klass.arel_table[column_name].eq_all(value)
  when :eq_any
    klass.arel_table[column_name].eq_any(value)
  when :gt
    klass.arel_table[column_name].gt(value)
  when :gt_all
    klass.arel_table[column_name].gt_all(value)
  when :gt_any
    klass.arel_table[column_name].gt_any(value)
  when :gteq
    klass.arel_table[column_name].gteq(value)
  when :gteq_all
    klass.arel_table[column_name].gteq_all(value)
  when :gteq_any
    klass.arel_table[column_name].gteq_any(value)
  when :in
    if value.is_a?(Array)
      klass.arel_table[column_name].in(value.flatten.compact.uniq)
    else
      klass.arel_table[column_name].in(value)
    end
  when :in_all
    if value.is_a?(Array)
      klass.arel_table[column_name].in_all(value.flatten.compact.uniq)
    else
      klass.arel_table[column_name].in_all(value)
    end
  when :in_any
    if value.is_a?(Array)
      klass.arel_table[column_name].in_any(value.flatten.compact.uniq)
    else
      klass.arel_table[column_name].in_any(value)
    end
  when :lt
    klass.arel_table[column_name].lt(value)
  when :lt_all
    klass.arel_table[column_name].lt_all(value)
  when :lt_any
    klass.arel_table[column_name].lt_any(value)
  when :lteq
    klass.arel_table[column_name].lteq(value)
  when :lteq_all
    klass.arel_table[column_name].lteq_all(value)
  when :lteq_any
    klass.arel_table[column_name].lteq_any(value)
  when :ilike
    klass.arel_table[column_name].matches("%#{value}%")
  when :matches
    klass.arel_table[column_name].matches(value)
  when :matches_all
    klass.arel_table[column_name].matches_all(value)
  when :matches_any
    klass.arel_table[column_name].matches_any(value)
  when :not_between
    klass.arel_table[column_name].not_between(value.first..value.last)
  when :not_eq
    klass.arel_table[column_name].not_eq(value)
  when :not_eq_all
    klass.arel_table[column_name].not_eq_all(value)
  when :not_eq_any
    klass.arel_table[column_name].not_eq_any(value)
  when :not_in
    klass.arel_table[column_name].not_in(value)
  when :not_in_all
    klass.arel_table[column_name].not_in_all(value)
  when :not_in_any
    klass.arel_table[column_name].not_in_any(value)
  else
    unless predicate.respond_to?(:call)
      raise ArgumentError,
            "unsupported predicate `#{predicate}`"
    end

    predicate.call(collection, value)
  end
end
valid_keys() click to toggle source

Protected instance methods

# File lib/fetcheable_on_api/filterable.rb, line 81
def valid_keys
  keys = filters_configuration.keys
  keys.each_with_index do |key, index|
    predicate = filters_configuration[key.to_sym].fetch(:with, :ilike)

    if(%i[between not_between in in_all in_any].include?(predicate))
      format = filters_configuration[key.to_sym].fetch(:format) { nil }
      keys[index] = {key => []} if format == :array
      next
    end

    next if predicate.respond_to?(:call) ||
            PREDICATES_WITH_ARRAY.exclude?(predicate.to_sym)

    keys[index] = {key => []}
  end

  keys
end