Created
January 29, 2021 20:16
-
-
Save andynu/ae216780ebc421d8880da04d55cb7203 to your computer and use it in GitHub Desktop.
Generic rails model filtering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def index | |
@records = Record.filter_params(params[:filters]) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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