If you’d like to add your own custom Ransack predicates:

# config/initializers/ransack.rb

Ransack.configure do |config|
  config.add_predicate 'equals_diddly', # Name your predicate
    # What non-compound ARel predicate will it use? (eq, matches, etc)
    arel_predicate: 'eq',
    # Format incoming values as you see fit. (Default: Don't do formatting)
    formatter: proc { |v| "#{v}-diddly" },
    # Validate a value. An "invalid" value won't be used in a search.
    # Below is default.
    validator: proc { |v| v.present? },
    # Should compounds be created? Will use the compound (any/all) version
    # of the arel_predicate to create a corresponding any/all version of
    # your predicate. (Default: true)
    compounds: true,
    # Force a specific column type for type-casting of supplied values.
    # (Default: use type from DB column)
    type: :string,
    # Use LOWER(column on database).
    # (Default: false)
    case_insensitive: true
end

You can check all Arel predicates here.

What a formatter returns

The formatter runs once per value and its return value is quoted as a single literal, so it cannot build a piece of SQL. For an in / not_in predicate that takes one delimited string, return an Array and Ransack quotes each element:

Ransack.configure do |config|
  config.add_predicate 'in_list', arel_predicate: 'in',
    formatter: proc { |v| v.split(';') }
end

Person.ransack(name_in_list: 'Aaron;Ernie').result.to_sql
# ... WHERE "people"."name" IN ('Aaron', 'Ernie')

Returning "Aaron','Ernie" instead gives IN ('Aaron'',''Ernie'): the string is one value, and treating it as SQL would be an injection vector.

If Arel does not have the predicate you are looking for, consider monkey patching it:

# config/initializers/ransack.rb

module Arel
  module Predications
    def gteq_or_null(other)
      left = gteq(other)
      right = eq(nil)
      left.or(right)
    end
  end
end

Ransack.configure do |config|
  config.add_predicate 'gteq_or_null', arel_predicate: 'gteq_or_null'
end