# frozen_string_literal: true module Lipstick module Filterable # Provides case-insensitive matching on a case-sensitive MySQL database # (which is mandated by Gumboot) class CollatedArelAttribute < Arel::Nodes::Node include Arel::Predications attr_reader :attribute, :collation def initialize(attribute, collation) @attribute = attribute @collation = collation end end module VisitCollatedArelAttribute # rubocop:disable Style/MethodName def visit_Lipstick_Filterable_CollatedArelAttribute(o, collector) visit(o.attribute, collector) collector << ' COLLATE ' << o.collation end # rubocop:enable Style/MethodName end Arel::Visitors::ToSql.include(VisitCollatedArelAttribute) module ClassMethods attr_reader :filterable_fields def filterable_by(*fields) @filterable_fields = fields end def filter(query) filter_terms(query).reduce(all) do |scope, term| conds = filterable_fields.map do |f| CollatedArelAttribute.new(arel_table[f], 'utf8_unicode_ci') .matches(term) end scope.where(conds.reduce { |a, e| a.or(e) }) end end private def filter_terms(query) query.to_s.downcase.split(/\s+/).map { |s| "*#{s}*".gsub(/[%*]+/, '%') } end end def self.included(base) base.extend(ClassMethods) end end end