metaminded/tabulatr2

View on GitHub
lib/tabulatr/data/filtering.rb

Summary

Maintainability
A
2 hrs
Test Coverage
#--
# Copyright (c) 2010-2014 Peter Horn & Florian Thomas, metaminded UG
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++

module Tabulatr::Data::Filtering

  def apply_search(query)
    like ||= Tabulatr::Utility.like_statement
    return unless query.present?
    if @search.is_a? Array
      query = query.strip.gsub(/['*%\s]+/, '%')
      a = @search.map do |name|
        column = table_columns.find{|c| c.name == name}
        nn = column ? column.col_options.filter_sql : name
        # nn = build_column_name name, use_for: :filter
        "(#{nn} #{like} '%#{query}%')"
      end
      a = a.join(' OR ')
      @relation = @relation.where(a)
    else # search is a proc
      execute_provided_search_block!(query)
    end
  end

  def apply_filters(filter_params)
    default_filters = table_columns.map do |col|
      next if col.col_options.default_filter.nil?
      ["#{col.table_name}:#{col.name}", col.col_options.default_filter]
    end.compact.to_h
    return unless filter_params || default_filters.present?
    filters = default_filters.merge(filter_params&.permit! || {})
    filters.to_hash.with_indifferent_access.each do |param|
      name, value = param
      next unless value.present?

      apply_condition(find_column(name), value)
    end
  end

  def apply_condition(n,v)
    # puts "FILTER: »#{n.filter.inspect}«, »#{n.filter.class}«"
    case n.filter
    when :checkbox then apply_boolean_condition(n, v)
    when :decimal  then apply_string_condition("#{n.col_options.filter_sql} = ?", v.to_f)
    when :integer, :enum  then apply_string_condition("#{n.col_options.filter_sql} = ?", v.to_i)
    when :enum_multiselect then apply_array_condition(n, v)
    when :exact, Hash, Array then apply_string_condition("#{n.col_options.filter_sql} = ?", v)
    when :like     then apply_like_condition(n, v['like'])
    when :date     then apply_date_condition(n, v['date'])
    when :range    then apply_range_condition(n, v)
    when :custom   then apply_custom_filter(n, v)
    else raise "Wrong filter type for #{n.name}: #{n.filter}"
    end
  end

  def apply_boolean_condition(column, value)
    @relation = @relation.where("#{column.col_options.filter_sql} = ?", Tabulatr::Utility.string_to_boolean(value))
  end

  def apply_date_condition(n, cond)
    today = Date.today
    yesterday = today - 1.day
    case cond['simple']
    when 'none' then return
    when 'today' then date_in_between(today, today.at_end_of_day, n)
    when 'yesterday' then date_in_between(yesterday, yesterday.at_end_of_day, n)
    when 'this_week' then date_in_between(today.at_beginning_of_week.beginning_of_day,
                            today.at_end_of_week.end_of_day, n)
    when 'last_7_days' then date_in_between((today - 6.day).beginning_of_day, today.at_end_of_day, n)
    when 'this_month' then date_in_between(today.at_beginning_of_month.beginning_of_day,
                              today.at_end_of_month.end_of_day, n)
    when 'last_30_days' then date_in_between((today - 29.day).beginning_of_day, today.at_end_of_day, n)
    when 'from_to' then date_in_between((Time.zone.parse(cond['from']) rescue nil), (Time.zone.parse(cond['to']) rescue nil), n)
    end
  end

  def apply_string_condition(replacement_string, value)
     @relation = @relation.where(replacement_string, value) if value.present?
  end

  def apply_like_condition(column, value)
    like ||= Tabulatr::Utility.like_statement
    apply_string_condition("#{column.col_options.filter_sql} #{like} ?", "%#{value}%") if value.present?
  end

  def apply_range_condition(column, hash)
    apply_string_condition("#{column.col_options.filter_sql} >= ?", "#{hash[:from]}")
    apply_string_condition("#{column.col_options.filter_sql} <= ?", "#{hash[:to]}")
  end

  def apply_array_condition(column, value)
    @relation = @relation.where(column.table_name => { column.name => value })
  end

  def apply_custom_filter(filter, value)
    filter_result = self.instance_exec @relation, value, &filter.block
    handle_search_result(filter_result)
  end

  private

  def execute_provided_search_block!(query)
    if @search.arity == 1
      search_result = @search.(query)
    elsif @search.arity == 2
      search_result = @search.(query, @relation)
    else
      raise 'Search block needs either `query` or both `query` and `relation` block variables'
    end
    handle_search_result(search_result)
  end

  def handle_search_result(search_result)
    return if search_result.nil?
    if search_result.is_a?(ActiveRecord::Relation)
      @relation = search_result
    elsif search_result.is_a?(String) || search_result.is_a?(Hash) || search_result.is_a?(Array)
      @relation = @relation.where(search_result)
    else
      Tabulatr::UnexpectedSearchResultError.raise_error(search_result.class)
    end
  end

  def date_in_between(from, to, column)
    @relation = @relation.where("#{column.col_options.filter_sql} >= ?", from) if from.present?
    @relation = @relation.where("#{column.col_options.filter_sql} <= ?", to) if to.present?
  end

  def find_column(name)
    table_name, method_name = name.split(':').map(&:to_sym)
    table_columns.find { |c| c.table_name == table_name && c.name == method_name } ||
      filters.find { |f| f.name.to_sym == name.to_sym }
  end

end

Tabulatr::Data.send :include, Tabulatr::Data::Filtering