module ActiveScaffold::Finder::ClassMethods
Public Class Methods
extended(klass)
click to toggle source
# File lib/active_scaffold/finder.rb, line 8 def self.extended(klass) return unless klass.active_scaffold_config if klass.active_scaffold_config.active_record? klass.extend ActiveRecord elsif klass.active_scaffold_config.mongoid? klass.extend Mongoid end end
Public Instance Methods
condition_for_column(column, value, text_search = :full)
click to toggle source
Generates an SQL condition for the given ActiveScaffold
column based on that column’s database type (or form_ui … for virtual columns?). TODO: this should reside on the column, not the controller
# File lib/active_scaffold/finder.rb, line 99 def condition_for_column(column, value, text_search = :full) like_pattern = like_pattern(text_search) value = value.with_indifferent_access if value.is_a? Hash if respond_to?("condition_for_#{column.name}_column") return send("condition_for_#{column.name}_column", column, value, like_pattern) end return unless column&.search_sql && value.present? search_ui = column.search_ui || column.column_type begin sql, *values = if search_ui && respond_to?("condition_for_#{search_ui}_type") send("condition_for_#{search_ui}_type", column, value, like_pattern) elsif column.search_sql.instance_of? Proc column.search_sql.call(value) else condition_for_search_ui(column, value, like_pattern, search_ui) end return nil unless sql where_values = [] sql_conditions = [] column.search_sql.each do |search_sql| if search_sql.is_a?(Hash) subquery_sql, *subquery_values = subquery_condition(column, sql, search_sql, values) sql_conditions << subquery_sql where_values.concat subquery_values else sql_conditions << sql % {:search_sql => search_sql} where_values.concat values end end [sql_conditions.join(' OR '), *where_values] rescue StandardError => e Rails.logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column :#{column.name}, search_ui = #{search_ui} in #{name}" raise e end end
condition_for_datetime(column, value, like_pattern = nil)
click to toggle source
# File lib/active_scaffold/finder.rb, line 360 def condition_for_datetime(column, value, like_pattern = nil) operator = ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value['opt']) && value['opt'] != 'BETWEEN' ? value['opt'] : nil from_value, to_value = datetime_from_to(column, value) if column.search_sql.is_a? Proc column.search_sql.call(from_value, to_value, operator) elsif operator.nil? ['%<search_sql>s BETWEEN ? AND ?', from_value, to_value] unless from_value.nil? || to_value.nil? else ["%<search_sql>s #{value['opt']} ?", from_value] unless from_value.nil? end end
condition_for_null_type(column, value, like_pattern = nil)
click to toggle source
# File lib/active_scaffold/finder.rb, line 459 def condition_for_null_type(column, value, like_pattern = nil) case value.to_s when 'null' ['%<search_sql>s is null', []] when 'not_null' ['%<search_sql>s is not null', []] end end
condition_for_numeric(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 181 def condition_for_numeric(column, value) if !value.is_a?(Hash) ['%<search_sql>s = ?', condition_value_for_numeric(column, value)] elsif ActiveScaffold::Finder::NULL_COMPARATORS.include?(value[:opt]) condition_for_null_type(column, value[:opt]) elsif value[:from].blank? || !ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt]) nil elsif value[:opt] == 'BETWEEN' ['(%<search_sql>s BETWEEN ? AND ?)', condition_value_for_numeric(column, value[:from]), condition_value_for_numeric(column, value[:to])] else ["%<search_sql>s #{value[:opt]} ?", condition_value_for_numeric(column, value[:from])] end end
condition_for_range(column, value, like_pattern = nil)
click to toggle source
# File lib/active_scaffold/finder.rb, line 195 def condition_for_range(column, value, like_pattern = nil) if !value.is_a?(Hash) if column.text? value = column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value ["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)] else ['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)] end elsif ActiveScaffold::Finder::NULL_COMPARATORS.include?(value[:opt]) condition_for_null_type(column, value[:opt], like_pattern) elsif value[:from].blank? nil elsif ActiveScaffold::Finder::STRING_COMPARATORS.values.include?(value[:opt]) text = column.active_record? ? column.active_record_class.sanitize_sql_like(value[:from]) : value[:from] [ "%<search_sql>s #{'NOT ' if value[:opt].start_with?('not_')}#{ActiveScaffold::Finder.like_operator} ?", value[:opt].sub('not_', '').sub('?', text) ] elsif value[:opt] == 'BETWEEN' ['(%<search_sql>s BETWEEN ? AND ?)', value[:from], value[:to]] elsif ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt]) ["%<search_sql>s #{value[:opt]} ?", value[:from]] end end
condition_for_record_select_type(column, value, like_pattern = nil)
click to toggle source
# File lib/active_scaffold/finder.rb, line 450 def condition_for_record_select_type(column, value, like_pattern = nil) if value.is_a?(Array) value = value.select(&:present?) ['%<search_sql>s IN (?)', value] if value.present? else ['%<search_sql>s = ?', value] end end
condition_for_search_ui(column, value, like_pattern, search_ui)
click to toggle source
# File lib/active_scaffold/finder.rb, line 154 def condition_for_search_ui(column, value, like_pattern, search_ui) case search_ui when :boolean, :checkbox if value == 'null' condition_for_null_type(column, value) else ['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value] end when :integer, :decimal, :float condition_for_numeric(column, value) when :string, :range condition_for_range(column, value, like_pattern) when :date, :time, :datetime, :timestamp condition_for_datetime(column, value) when :select, :select_multiple, :draggable, :multi_select, :country, :usa_state, :chosen, :multi_chosen values = Array(value).select(&:present?) ['%<search_sql>s in (?)', values] if values.present? else if column.text? value = column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value ["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)] else ['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)] end end end
condition_value_for_datetime(column, value, conversion = :to_time)
click to toggle source
# File lib/active_scaffold/finder.rb, line 313 def condition_value_for_datetime(column, value, conversion = :to_time) return if value.nil? || value.blank? if value.is_a? Hash local_time_from_hash(value, conversion) elsif value.respond_to?(:strftime) if conversion == :to_time # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC. # https://github.com/rails/rails/pull/2453 value.to_time.in_time_zone else value.send(conversion) end elsif conversion == :to_date parse_date_with_format(*format_for_date(column, value)) elsif value.include?('T') Time.zone.parse(value) else # datetime time = parse_time_with_format(value, *format_for_datetime(column, value)) conversion == :to_time ? time : time.send(conversion) end end
condition_value_for_numeric(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 335 def condition_value_for_numeric(column, value) return value if value.nil? value = column.number_to_native(value) if column.options[:format] && column.search_ui != :number case (column.search_ui || column.column_type) when :integer then if value.is_a?(TrueClass) || value.is_a?(FalseClass) value ? 1 : 0 else value.to_i end when :float then value.to_f when :decimal then ::ActiveRecord::Type::Decimal.new.cast(value) else value end end
conditions_for_columns(tokens, columns, text_search = :full)
click to toggle source
Takes a collection of search terms (the tokens) and creates SQL that searches all specified ActiveScaffold
columns. A row will match if each token is found in at least one of the columns.
# File lib/active_scaffold/finder.rb, line 20 def conditions_for_columns(tokens, columns, text_search = :full) # if there aren't any columns, then just return a nil condition return unless columns.any? tokens = [tokens] if tokens.is_a? String tokens = type_casted_tokens(tokens, columns, like_pattern(text_search)) create_conditions_for_columns(tokens, columns) end
datetime_column_date?(column)
click to toggle source
# File lib/active_scaffold/finder.rb, line 446 def datetime_column_date?(column) column.column&.type == :date end
datetime_conversion_for_condition(column)
click to toggle source
# File lib/active_scaffold/finder.rb, line 352 def datetime_conversion_for_condition(column) if column.column column.column_type == :date ? :to_date : :to_time else :to_time end end
datetime_from_to(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 373 def datetime_from_to(column, value) conversion = datetime_conversion_for_condition(column) case value['opt'] when 'RANGE' values = datetime_from_to_for_range(column, value) # Avoid calling to_time, not needed and broken on rails >= 4, because return local time instead of UTC values.collect!(&conversion) if conversion != :to_time values when 'PAST', 'FUTURE' values = datetime_from_to_for_trend(column, value) # Avoid calling to_time, not needed and broken on rails >= 4, because return local time instead of UTC values.collect!(&conversion) if conversion != :to_time values else %w[from to].collect { |field| condition_value_for_datetime(column, value[field], conversion) } end end
datetime_from_to_for_range(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 422 def datetime_from_to_for_range(column, value) case value['range'] when 'TODAY' [datetime_now.beginning_of_day, datetime_now.end_of_day] when 'YESTERDAY' [datetime_now.ago(1.day).beginning_of_day, datetime_now.ago(1.day).end_of_day] when 'TOMORROW' [datetime_now.in(1.day).beginning_of_day, datetime_now.in(1.day).end_of_day] else range_type, range = value['range'].downcase.split('_') raise ArgumentError unless %w[week month year].include?(range) case range_type when 'this' return datetime_now.send("beginning_of_#{range}".to_sym), datetime_now.send("end_of_#{range}") when 'prev' return datetime_now.ago(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), datetime_now.ago(1.send(range.to_sym)).send("end_of_#{range}".to_sym) when 'next' return datetime_now.in(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), datetime_now.in(1.send(range.to_sym)).send("end_of_#{range}".to_sym) else return nil, nil end end end
datetime_from_to_for_trend(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 395 def datetime_from_to_for_trend(column, value) case value['opt'] when 'PAST' trend_number = [value['number'].to_i, 1].max now = datetime_now if datetime_column_date?(column) from = now.beginning_of_day.ago(trend_number.send(value['unit'].downcase.singularize.to_sym)) to = now.end_of_day else from = now.ago(trend_number.send(value['unit'].downcase.singularize.to_sym)) to = now end [from, to] when 'FUTURE' trend_number = [value['number'].to_i, 1].max now = datetime_now if datetime_column_date?(column) from = now.beginning_of_day to = now.end_of_day.in(trend_number.send(value['unit'].downcase.singularize.to_sym)) else from = now to = now.in(trend_number.send(value['unit'].downcase.singularize.to_sym)) end [from, to] end end
datetime_now()
click to toggle source
# File lib/active_scaffold/finder.rb, line 391 def datetime_now Time.zone.now end
format_for_date(column, value, format_name = column.options[:format])
click to toggle source
# File lib/active_scaffold/finder.rb, line 282 def format_for_date(column, value, format_name = column.options[:format]) if format_name format = I18n.t("date.formats.#{format_name}") format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m en_value = I18n.locale == :en ? value : translate_days_and_months(value, format) end [en_value || value, format] end
format_for_datetime(column, value)
click to toggle source
# File lib/active_scaffold/finder.rb, line 251 def format_for_datetime(column, value) parts = Date._parse(value) if ActiveScaffold.js_framework == :jquery format = I18n.translate "time.formats.#{column.options[:format] || :picker}", :default => '' end if format.blank? time_parts = [[:hour, '%H'], [:min, '%M'], [:sec, '%S']].map do |part, format_part| format_part if parts[part].present? end.compact format = "#{I18n.t('date.formats.default')} #{time_parts.join(':')} #{'%z' if parts[:offset].present?}" else [[:hour, '%H'], [:min, ':%M'], [:sec, ':%S']].each do |part, f| format.gsub!(f, '') if parts[part].blank? end format += ' %z' if parts[:offset].present? && format !~ /%z/i end format.gsub!(/.*(?=%H)/, '') if !parts[:year] && !parts[:month] && !parts[:mday] [format, parts[:offset]] end
local_time_from_hash(value, conversion = :to_time)
click to toggle source
# File lib/active_scaffold/finder.rb, line 273 def local_time_from_hash(value, conversion = :to_time) time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i }) time.send(conversion) rescue StandardError => e message = "Error creating time from #{value.inspect}:" Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}" nil end
parse_date_with_format(value, format)
click to toggle source
# File lib/active_scaffold/finder.rb, line 291 def parse_date_with_format(value, format) Date.strptime(value, *format) rescue StandardError => e message = "Error parsing date from #{value}" message << ", with format #{format}" if format Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}" nil end
parse_time_with_format(value, format, offset)
click to toggle source
# File lib/active_scaffold/finder.rb, line 300 def parse_time_with_format(value, format, offset) format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m en_value = I18n.locale == :en ? value : translate_days_and_months(value, format) time = Time.strptime(en_value, format) offset ? time : Time.zone.local_to_utc(time).in_time_zone rescue StandardError => e message = "Error parsing time from #{en_value}" message << " (#{value})" if en_value != value message << ", with format #{format}" if format Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}" nil end
subquery_condition(column, sql, options, values)
click to toggle source
# File lib/active_scaffold/finder.rb, line 137 def subquery_condition(column, sql, options, values) relation, *columns = options[:subquery] conditions = [columns.map { |search_sql| sql % {:search_sql => search_sql} }.join(' OR ')] conditions += values * columns.size if values.present? subquery = relation.where(conditions) subquery = subquery.select(relation.primary_key) if subquery.select_values.blank? conditions = [["#{options[:field] || column.field} IN (?)", options[:conditions]&.first].compact.join(' AND ')] conditions << subquery conditions.concat options[:conditions][1..-1] if options[:conditions] if column.association&.polymorphic? conditions[0] << " AND #{column.quoted_foreign_type} = ?" conditions << relation.base_class.sti_name end conditions end
tables_for_translating_days_and_months(format)
click to toggle source
# File lib/active_scaffold/finder.rb, line 220 def tables_for_translating_days_and_months(format) # rubocop:disable Style/FormatStringToken keys = { '%A' => 'date.day_names', '%a' => 'date.abbr_day_names', '%B' => 'date.month_names', '%b' => 'date.abbr_month_names' } # rubocop:enable Style/FormatStringToken key_index = keys.keys.map { |key| [key, format.index(key)] }.to_h keys.select! { |k, _| key_index[k] } keys.sort_by { |k, _| key_index[k] }.map do |_, k| I18n.t(k).compact.zip(I18n.t(k, :locale => :en).compact).to_h end end
translate_days_and_months(value, format)
click to toggle source
# File lib/active_scaffold/finder.rb, line 236 def translate_days_and_months(value, format) translated = '' tables_for_translating_days_and_months(format).each do |table| regexp = Regexp.union(table.keys) index = value.index(regexp) next unless index translated << value.slice!(0...index) value.sub!(regexp) do |str| translated << table[str] '' end end translated << value end
type_casted_tokens(tokens, columns, like_pattern)
click to toggle source
# File lib/active_scaffold/finder.rb, line 29 def type_casted_tokens(tokens, columns, like_pattern) tokens.map do |value| columns.each_with_object({}) do |column, column_tokens| column_tokens[column.name] = if column.text? like_pattern.sub('?', column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value) else ActiveScaffold::Core.column_type_cast(value, column.column) end end end end