jbox-web/ajax-datatables-rails

View on GitHub
lib/ajax-datatables-rails/datatable/column/search.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
93%
# frozen_string_literal: true

module AjaxDatatablesRails
  module Datatable
    class Column
      module Search

        SMALLEST_PQ_INTEGER = -2_147_483_648
        LARGEST_PQ_INTEGER  = 2_147_483_647
        NOT_NULL_VALUE      = '!NULL'
        EMPTY_VALUE         = ''

        def searchable?
          @view_column.fetch(:searchable, true)
        end

        def cond
          @view_column.fetch(:cond, :like)
        end

        def filter
          @view_column[:cond].call(self, formatted_value)
        end

        def search
          @search ||= SimpleSearch.new(options[:search])
        end

        def searched?
          search.value.present?
        end

        def search_query
          search.regexp? ? regex_search : non_regex_search
        end

        # Add use_regex option to allow bypassing of regex search
        def use_regex?
          @view_column.fetch(:use_regex, true)
        end

        private

        # Using multi-select filters in JQuery Datatable auto-enables regex_search.
        # Unfortunately regex_search doesn't work when filtering on primary keys with integer.
        # It generates this kind of query : AND ("regions"."id" ~ '2|3') which throws an error :
        # operator doesn't exist : integer ~ unknown
        # The solution is to bypass regex_search and use non_regex_search with :in operator
        def regex_search
          if use_regex?
            ::Arel::Nodes::Regexp.new((custom_field? ? field : table[field]), ::Arel::Nodes.build_quoted(formatted_value))
          else
            non_regex_search
          end
        end

        # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
        def non_regex_search
          case cond
          when Proc
            filter
          when :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in
            searchable_integer? ? raw_search(cond) : empty_search
          when :start_with
            text_search("#{formatted_value}%")
          when :end_with
            text_search("%#{formatted_value}")
          when :like
            text_search("%#{formatted_value}%")
          when :string_eq
            raw_search(:eq)
          when :string_in
            raw_search(:in)
          when :null_value
            null_value_search
          when :date_range
            date_range_search
          end
        end
        # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity

        def null_value_search
          if formatted_value == NOT_NULL_VALUE
            table[field].not_eq(nil)
          else
            table[field].eq(nil)
          end
        end

        def raw_search(cond)
          table[field].send(cond, formatted_value) unless custom_field?
        end

        def text_search(value)
          casted_column.matches(value) unless custom_field?
        end

        def empty_search
          casted_column.matches(EMPTY_VALUE)
        end

        def searchable_integer?
          if formatted_value.is_a?(Array)
            valids = formatted_value.map { |v| integer?(v) && !out_of_range?(v) }
            !valids.include?(false)
          else
            integer?(formatted_value) && !out_of_range?(formatted_value)
          end
        end

        def out_of_range?(search_value)
          Integer(search_value) > LARGEST_PQ_INTEGER || Integer(search_value) < SMALLEST_PQ_INTEGER
        end

        def integer?(string)
          Integer(string)
          true
        rescue ArgumentError
          false
        end

      end
    end
  end
end