class DbTextSearch::CaseInsensitive

Provides case-insensitive string-in-set querying, LIKE querying, and CI index creation.

Public Class Methods

adapter_class(connection, table_name, column_name) click to toggle source

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param table_name [String, Symbol] @param column_name [String, Symbol] @return [Class<AbstractAdapter>] @api private

# File lib/db_text_search/case_insensitive.rb, line 80
def self.adapter_class(connection, table_name, column_name)
  lower_or_insensitive = -> {
    column_case_sensitive?(connection, table_name, column_name) ? LowerAdapter : InsensitiveColumnAdapter
  }
  DbTextSearch.match_adapter(
      connection,
      mysql:    lower_or_insensitive,
      postgres: lower_or_insensitive,
      # Always use COLLATE NOCASE for SQLite, as we can't check if the column is case-sensitive.
      # It has no performance impact apart from slightly longer query strings for case-insensitive columns.
      sqlite:   -> { CollateNocaseAdapter }
  )
end
add_ci_text_column(connection, table_name, column_name, options = {}) click to toggle source

Adds a case-insensitive column to the given table. @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param table_name [String, Symbol] @param column_name [String, Symbol] @param options [Hash] passed to ActiveRecord::ConnectionAdapters::SchemaStatements#add_index

# File lib/db_text_search/case_insensitive.rb, line 44
def self.add_ci_text_column(connection, table_name, column_name, options = {})
  type, options = DbTextSearch.match_adapter(
      connection,
      mysql:    -> { [:text, options] },
      postgres: -> {
        connection.enable_extension 'citext'
        [(ActiveRecord::VERSION::STRING >= '4.2.0' ? :citext : 'CITEXT'), options]
      },
      sqlite:   -> {
        if ActiveRecord::VERSION::MAJOR >= 5
          [:text, options.merge(collation: 'NOCASE')]
        else
          ['TEXT COLLATE NOCASE', options]
        end
      }
  )
  connection.add_column table_name, column_name, type, **options
end
add_index(connection, table_name, column_name, options = {}) click to toggle source

Add an index for case-insensitive string search.

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param table_name [String, Symbol] @param column_name [String, Symbol] @param options [Hash] @option options name [String] index name @option options unique [Boolean] default: false

# File lib/db_text_search/case_insensitive.rb, line 71
def self.add_index(connection, table_name, column_name, options = {})
  adapter_class(connection, table_name, column_name).add_index(connection, table_name, column_name, options)
end
column_case_sensitive?(connection, table_name, column_name) click to toggle source

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param table_name [String, Symbol] @param column_name [String, Symbol] @return [Boolean] @note sqlite not supported. @api private

# File lib/db_text_search/case_insensitive.rb, line 100
def self.column_case_sensitive?(connection, table_name, column_name) # rubocop:disable Metrics/AbcSize
  column = connection.schema_cache.columns(table_name).detect { |c| c.name == column_name.to_s }
  fail "Column #{column_name.to_s.inspect} not found on table #{table_name.inspect}" if column.nil?
  DbTextSearch.match_adapter(
      connection,
      mysql:    -> { column.case_sensitive? },
      postgres: -> { column.sql_type !~ /citext/i },
      sqlite:   -> { DbTextSearch.unsupported_adapter! connection }
  )
end
new(scope, column) click to toggle source

@param scope [ActiveRecord::Relation, Class<ActiveRecord::Base>] @param column [Symbol] name

# File lib/db_text_search/case_insensitive.rb, line 12
def initialize(scope, column)
  @adapter = self.class.adapter_class(scope.connection, scope.table_name, column).new(scope, column)
  @scope   = scope
end

Public Instance Methods

column_for_order(asc_or_desc) click to toggle source

@param asc_or_desc [Symbol] @return [Arel::Collectors::SQLString] a string to be used within an ‘.order()`

# File lib/db_text_search/case_insensitive.rb, line 34
def column_for_order(asc_or_desc)
  fail 'Pass either :asc or :desc' unless %i[asc desc].include?(asc_or_desc)
  @adapter.column_for_order(asc_or_desc)
end
in(value_or_values) click to toggle source

@param value_or_values [String, Array<String>] @return [ActiveRecord::Relation]

# File lib/db_text_search/case_insensitive.rb, line 19
def in(value_or_values)
  values = Array(value_or_values)
  return @scope.none if values.empty?
  @adapter.in(values)
end
prefix(query) click to toggle source

@param query [String] @return [ActiveRecord::Relation] the scope of records with matching prefix.

# File lib/db_text_search/case_insensitive.rb, line 27
def prefix(query)
  return @scope.none if query.empty?
  @adapter.prefix(query)
end