Skip to content

Instantly share code, notes, and snippets.

@andynu
Created January 29, 2021 20:16
Show Gist options
  • Save andynu/ae216780ebc421d8880da04d55cb7203 to your computer and use it in GitHub Desktop.
Save andynu/ae216780ebc421d8880da04d55cb7203 to your computer and use it in GitHub Desktop.
Generic rails model filtering
def index
@records = Record.filter_params(params[:filters])
end
require 'active_support/concern'
# The {FilterConcern} provides a `filter` scope which takes a hash in the form { col1: term, col2: term }
# This does type sensitive filtering supporting:
# * strings - fuzzy search of %term%
# * dates -
# * #### => year,
# * ##/#### => month/year,
# * ##/##/#### => full date
# * other - exact term matching
#
# For composite columns { "col_a,col_b" => term } it will attempt to call a
# custom scope
#
# scope :filter_col_a_col_b, (term) -> { ... }
#
module FiltersConcern
extend ActiveSupport::Concern
included do
# filters is a hash of { column => search_value, ... }
# Strings fuzzy search. Everything else is an exact match.
scope :filter_params, ->(filters, table_prefix: nil) {
table_prefix = "#{table_prefix}." unless table_prefix.nil?
query = all
if filters.present?
filters.each_pair do |column, value|
if value.blank?
logger.debug "Filtering: #{column} => ALL (value='#{value}')"
next
end
custom_filter_scope = "filter_#{column.gsub(/[^[:alnum:]]/, '_').squeeze('_')}".to_sym
if self.respond_to? custom_filter_scope
logger.debug("Filtering: using custom scope #{custom_filter_scope}('#{value}')")
query = query.send(custom_filter_scope, value)
elsif column.in?(attribute_names)
col_type = query.type_for_attribute(column).type
logger.debug "Filtering: #{column}=#{value} (type: #{col_type})"
case col_type
when :string
query = query.where("#{table_prefix}#{column} like ?", "%#{value}%")
when :date, :datetime
slashes = value.scan(/\//).count
case slashes
when 0
year = value.to_i
query = query.where("year(#{table_prefix}#{column}) = ?", year)
when 1
month, year = value.split(/\//).map(&:to_i)
query = query.where("year(#{table_prefix}#{column}) = ? and month(#{table_prefix}#{column}) = ?", year, month)
when 2
month, day, year = value.split(/\//).map(&:to_i)
query = query.where("year(#{table_prefix}#{column}) = ? and month(#{table_prefix}#{column}) = ? and day(#{table_prefix}#{column}) = ?", year, month, day)
else
logger.debug "Too many slashes. Unkown date format: '#{value}'"
end
else
query = query.where(column => value)
end
else
logger.warn "Could not filter composite column #{column}. To handle this add a custom scope :#{custom_filter_scope}, ->(term){ ... }"
end
end
else
logger.debug 'no filters'
query
end
query
}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment