module ActiveScaffold::Finder::ClassMethods

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 38
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 self.respond_to?("condition_for_#{column.name}_column")
    return send("condition_for_#{column.name}_column", column, value, like_pattern)
  end
  return unless column && column.search_sql && !value.blank?
  search_ui = column.search_ui || column.column.try(:type)
  begin
    sql, *values =
      if search_ui && self.respond_to?("condition_for_#{search_ui}_type")
        send("condition_for_#{search_ui}_type", column, value, like_pattern)
      else
        if column.search_sql.instance_of? Proc
          column.search_sql.call(value)
        else
          condition_for_search_ui(column, value, like_pattern, search_ui)
        end
      end
    return nil unless sql

    conditions = [column.search_sql.collect { |search_sql| sql % {:search_sql => search_sql} }.join(' OR ')]
    conditions += values * column.search_sql.size if values.present?
    conditions
  rescue StandardError => e
    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 199
def condition_for_datetime(column, value, like_pattern = nil)
  conversion = datetime_conversion_for_condition(column)
  from_value = condition_value_for_datetime(column, value[:from], conversion)
  to_value = condition_value_for_datetime(column, value[:to], conversion)

  if from_value.nil? && to_value.nil?
    nil
  elsif !from_value
    ['%{search_sql} <= ?', to_value.to_s(:db)]
  elsif !to_value
    ['%{search_sql} >= ?', from_value.to_s(:db)]
  else
    ['%{search_sql} BETWEEN ? AND ?', from_value.to_s(:db), to_value.to_s(:db)]
  end
end
condition_for_null_type(column, value, like_pattern = nil) click to toggle source
# File lib/active_scaffold/finder.rb, line 223
def condition_for_null_type(column, value, like_pattern = nil)
  case value.to_s
  when 'null'
    ['%{search_sql} is null', []]
  when 'not_null'
    ['%{search_sql} is not null', []]
  end
end
condition_for_numeric(column, value) click to toggle source
# File lib/active_scaffold/finder.rb, line 89
def condition_for_numeric(column, value)
  if !value.is_a?(Hash)
    ['%{search_sql} = ?', 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} BETWEEN ? AND ?)', condition_value_for_numeric(column, value[:from]), condition_value_for_numeric(column, value[:to])]
  else
    ["%{search_sql} #{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 103
def condition_for_range(column, value, like_pattern = nil)
  if !value.is_a?(Hash)
    if column.text?
      ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
    else
      ['%{search_sql} = ?', 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])
    ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", value[:opt].sub('?', value[:from])]
  elsif value[:opt] == 'BETWEEN'
    ['(%{search_sql} BETWEEN ? AND ?)', value[:from], value[:to]]
  elsif ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt])
    ["%{search_sql} #{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 215
def condition_for_record_select_type(column, value, like_pattern = nil)
  if value.is_a?(Array)
    ['%{search_sql} IN (?)', value]
  else
    ['%{search_sql} = ?', value]
  end
end
condition_for_search_ui(column, value, like_pattern, search_ui) click to toggle source
# File lib/active_scaffold/finder.rb, line 68
def condition_for_search_ui(column, value, like_pattern, search_ui)
  case search_ui
  when :boolean, :checkbox
    ['%{search_sql} = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
  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, :multi_select, :country, :usa_state, :chosen, :multi_chosen
    ['%{search_sql} in (?)', Array(value)]
  else
    if column.text?
      ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
    else
      ['%{search_sql} = ?', 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 139
def condition_value_for_datetime(column, value, conversion = :to_time)
  if value.is_a? Hash
    Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect { |part| value[part].to_i }) rescue nil
  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
    Date.strptime(value, I18n.t("date.formats.#{column.options[:format] || :default}")) rescue nil
  else
    parts = Date._parse(value)
    format = I18n.translate "time.formats.#{column.options[:format] || :picker}", :default => '' if ActiveScaffold.js_framework == :jquery
    if format.blank?
      time_parts = [[:hour, '%H'], [:min, '%M'], [:sec, '%S']].collect { |part, format_part| format_part if parts[part].present? }.compact
      format = "#{I18n.t('date.formats.default')} #{time_parts.join(':')} #{'%z' if parts[:offset].present?}"
    else
      if parts[:hour]
        [[:min, '%M'], [:sec, '%S']].each { |part, f| format.gsub!(":#{f}", '') unless parts[part].present? }
      else
        value += ' 00:00:00'
      end
      format += ' %z' if parts[:offset].present? && format !~ /%z/i
    end
    if !parts[:year] && !parts[:month] && !parts[:mday]
      value = "#{Date.today.strftime(format.gsub(/%[HI].*/, ''))} #{value}"
    end
    value = translate_days_and_months(value, format) if I18n.locale != :en
    time = DateTime.strptime(value, format) rescue nil
    if time
      time = Time.zone.local_to_utc(time).in_time_zone unless parts[:offset]
      time = time.send(conversion) unless conversion == :to_time
    end
    time
  end unless value.nil? || value.blank?
end
condition_value_for_numeric(column, value) click to toggle source
# File lib/active_scaffold/finder.rb, line 179
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 value.to_i rescue value ? 1 : 0
  when :float     then value.to_f
  when :decimal   then ActiveRecord::ConnectionAdapters::Column.value_to_decimal(value)
  else
    value
  end
end
create_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 11
def create_conditions_for_columns(tokens, columns, text_search = :full)
  # if there aren't any columns, then just return a nil condition
  return unless columns.any?
  like_pattern = like_pattern(text_search)

  tokens = [tokens] if tokens.is_a? String

  where_clauses = []
  columns.each do |column|
    Array(column.search_sql).each do |search_sql|
      where_clauses << "#{search_sql} #{column.text? ? ActiveScaffold::Finder.like_operator : '='} ?"
    end
  end
  phrase = where_clauses.join(' OR ')

  tokens.collect do |value|
    columns.each_with_object([phrase]) do |column, condition|
      Array(column.search_sql).size.times do
        condition.push(column.text? ? like_pattern.sub('?', value) : ActiveScaffold::Core.column_type_cast(value, column.column))
      end
    end
  end
end
datetime_conversion_for_condition(column) click to toggle source
# File lib/active_scaffold/finder.rb, line 191
def datetime_conversion_for_condition(column)
  if column.column
    column.column.type == :date ? :to_date : :to_time
  else
    :to_time
  end
end
like_pattern(text_search) click to toggle source
# File lib/active_scaffold/finder.rb, line 232
def like_pattern(text_search)
  case text_search
    when :full then '%?%'
    when :start then '?%'
    when :end then '%?'
    else '?'
  end
end
translate_days_and_months(value, format) click to toggle source
# File lib/active_scaffold/finder.rb, line 123
def translate_days_and_months(value, format)
  keys = {
    '%A' => 'date.day_names',
    '%a' => 'date.abbr_day_names',
    '%B' => 'date.month_names',
    '%b' => 'date.abbr_month_names'
  }
  keys.each do |f, k|
    if format.include? f
      table = Hash[I18n.t(k).compact.zip(I18n.t(k, :locale => :en).compact)]
      value.gsub!(Regexp.union(table.keys)) { |s| table[s] }
    end
  end
  value
end