#-- # 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