class Ransack::Context

def allowlist(klass, method, *fallbacks)

model's default runs, which raises asking for a list (#1403).
block's list of everything that exists is used, and otherwise the
decision to the controller: when the parameters were permitted, the
the controller permitted. A model that defines none has left the
own `method` is always asked, so its list applies on top of whatever
The allowlist a search consults on `klass`. A model that defines its
def allowlist(klass, method, *fallbacks)
  if permitted && [method, *fallbacks].none? { |m| klass.ransackable_list_defined?(m) }
    yield
  else
    klass.public_send(method, auth_object)
  end
end

def association_path(str, base = @base)

def association_path(str, base = @base)
  return ''.freeze unless key_within_depth_limit?(str)
  base = klassify(base)
  str ||= ''.freeze
  path = []
  segments = str.split(Constants::UNDERSCORE)
  association_parts = []
  unless segments.empty?
    while !segments.empty? &&
          !base.columns_hash[segments.join(Constants::UNDERSCORE)] &&
          association_parts << segments.shift
      assoc, klass = unpolymorphize_association(
        association_parts.join(Constants::UNDERSCORE)
      )
      next unless found_assoc = get_association(assoc, base)
      # A polymorphic association is only complete once its
      # `_of_Model_type` suffix has been consumed; matching on the bare
      # name would ask the reflection for a class it cannot know (#1557).
      next if found_assoc.polymorphic? && klass.nil?
      path += association_parts
      association_parts = []
      base = klassify(klass || found_assoc)
    end
  end
  path.join(Constants::UNDERSCORE)
end

def associations_for(klass)

def associations_for(klass)
  allowlist(klass, :ransackable_associations) { klass.authorizable_ransackable_associations }
end

def attributes_for(klass)

def attributes_for(klass)
  allowlist(klass, :ransackable_attributes) { klass.authorizable_ransackable_attributes }
end

def bind(object, str)

def bind(object, str)
  return nil unless str
  object.parent, object.attr_name = bind_pair_for(str)
end

def bind_pair_for(key)

def bind_pair_for(key)
  @bind_pairs ||= {}
  @bind_pairs[key] ||= begin
    parent, attr_name = get_parent_and_attribute_name(key.to_s)
    [parent, attr_name] if parent && attr_name
  end
end

def cast_scope_args(args)

def cast_scope_args(args)
  if args.is_a?(Array)
    args = args.map(&method(:cast_scope_args))
  end
  if Constants::TRUE_VALUES.include? args
    true
  elsif Constants::FALSE_VALUES.include? args
    false
  else
    args
  end
end

def chain_scope(scope, args)

by a yes / no / any select (#1375).
given, so `false` reaches it like any other value and it can be driven
`ransackable_scopes_skip_sanitize_args` has asked to see its values as
scope is not applied at all. A scope listed in
A bare `false` normally means "this checkbox was not ticked" and the
def chain_scope(scope, args)
  return unless @klass.method(scope)
  return if args == false && !ransackable_scope_skip_sanitize_args?(scope, @klass)
  @object = if scope_arity(scope) < 1 && args == true
              @object.public_send(scope)
            elsif scope_arity(scope) == 1 && args.is_a?(Array)
              # For scopes with arity 1, pass the array as a single argument instead of splatting
              @object.public_send(scope, args)
            else
              @object.public_send(scope, *args)
            end
end

def contextualize(str)

into the attribute itself
Convert a string representing a chain of associations and an attribute
def contextualize(str)
  parent, attr_name = bind_pair_for(str)
  table_for(parent)[attr_name]
end

def dialect

Ransack::ActiveRecord::Dialect for the questions it answers.
where generated SQL differs by database. See
The SQL dialect the search will be rendered in, for the few places
def dialect
  raise NotImplementedError, "#{self.class} must implement #dialect"
end

def for(object, options = {})

def for(object, options = {})
  Ransack::Context.resolvers.each do |resolver|
    context = resolver.call(object, options)
    return context if context
  end
  raise ArgumentError, "Don't know what context to use for #{object}"
end

def initialize(object, options = {})

def initialize(object, options = {})
  @object = relation_for(object)
  @klass = @object.klass
  @join_type = options[:join_type] || Arel::Nodes::OuterJoin
  @search_key = options[:search_key] || Ransack.options[:search_key]
  @associations_pot = {}
  @tables_pot = {}
  @lock_associations = []
  @join_dependency = join_dependency(@object)
  @base = @join_dependency.instance_variable_get(:@join_root)
end

def key_within_depth_limit?(key)

(GHSA-j3f8-w227-4hh8).
an over-long key is treated as unknown rather than parsed
superlinear in the segment count and the key is attacker-controlled, so
parse. The attribute / association / predicate parsing that follows is
False when a key has more `_`-separated segments than Ransack will
def key_within_depth_limit?(key)
  key.to_s.count(Constants::UNDERSCORE) <= Constants::MAX_KEY_DEPTH
end

def klassify(obj)

on the integration's Context subclass.
Each ORM integration decides what counts as a model, so this is defined
The model class behind a search object, an association node or a class.
def klassify(obj)
  raise NotImplementedError, "#{self.class} must implement #klassify"
end

def null_sentinel

this context does not change the conditions of an earlier one.
Condition captures this when it is built, so a later search reusing
set, including to `false` to turn the feature off for one search. A
Falls back to the global option unless this search's own option was
def null_sentinel
  @null_sentinel.nil? ? Ransack.options[:null_sentinel] : @null_sentinel
end

def polymorphic_class(name)

(#1738).
`Kernel.const_get` used to raise `NameError` straight from the URL
raised as `InvalidSearchError` under `ignore_unknown_conditions`.
and the whole key fails as any other unknown attribute does, ignored or
model to search, so the suffix is then no polymorphic reference at all
something that is no module at all (`ENV::Person`). None of those is a
lowercase word, a module, a class with no table, a path through
string, so it can name anything: a constant that does not exist, a
The class named in an `_of_Model_type` suffix comes from the query
def polymorphic_class(name)
  klass = name.safe_constantize
  klass if klass.is_a?(Class) && searchable_class?(klass)
rescue TypeError
  # `safe_constantize` swallows a missing constant but not a namespace
  # that is not a module, which Ruby reports as a TypeError.
  nil
end

def ransackable_alias(str)

def ransackable_alias(str)
  klass._ransack_aliases.fetch(str, klass._ransack_aliases.fetch(str.to_sym, str))
end

def ransackable_association?(str, klass)

def ransackable_association?(str, klass)
  associations_for(klass).any? { |s| s.to_sym == str.to_sym }
end

def ransackable_attribute?(str, klass)

def ransackable_attribute?(str, klass)
  attributes_for(klass).any? { |s| s.to_sym == str.to_sym }
end

def ransackable_scope?(str, klass)

def ransackable_scope?(str, klass)
  klass.ransackable_scopes(auth_object).any? { |s| s.to_sym == str.to_sym }
end

def ransackable_scope_skip_sanitize_args?(str, klass)

def ransackable_scope_skip_sanitize_args?(str, klass)
  klass.ransackable_scopes_skip_sanitize_args.any? { |s| s.to_sym == str.to_sym }
end

def ransortable_attribute?(str, klass)

def ransortable_attribute?(str, klass)
  sortable_attributes_for(klass).any? { |s| s.to_sym == str.to_sym }
end

def register(&resolver)

integration for another ORM registers its own resolver here.
Active Record integration (see Ransack::ActiveRecord::Context); an
objects it understands and nil for anything else. Ransack ships the
An ORM integration registers a block that returns a Context for the
def register(&resolver)
  Ransack::Context.resolvers << resolver
end

def resolve_alias_segment(segment)

def resolve_alias_segment(segment)
  target = ransackable_alias(segment)
  return target if target != segment
  path = association_path(segment)
  return segment if path.blank?
  remainder = segment.delete_prefix("#{path}_")
  associated = traverse(path)
  target = associated._ransack_aliases.fetch(remainder, associated._ransack_aliases.fetch(remainder.to_sym, remainder))
  return segment if target == remainder
  # The target may itself be one attribute whose name contains a
  # combinator (`terms_and_conditions`); it is prefixed whole.
  return "#{path}_#{target}" if ransackable_attribute?(target, associated)
  # Prefix every attribute of the alias's target with the path.
  target.gsub(/(\A|_and_|_or_)/) { "#{$1}#{path}_" }
end

def resolve_aliases(str)

holds no alias.
`name_or_email_or_parent_name`. Returns the name unchanged when it
`term_or_daddy` with `term => name_or_email` becomes
compound is joined with that compound's own combinator, so
(`person_term` when Person aliases `term`). An alias that expands to a
associated model and reached through an association path
`_or_` / `_and_` segment of a compound, and an alias defined on an
Expands every `ransack_alias` in a condition name: the whole name, each
def resolve_aliases(str)
  return str.to_s unless key_within_depth_limit?(str)
  whole = resolve_alias_segment(str)
  return whole if whole != str
  # A name that is an attribute in its own right is never split, even if
  # it contains a combinator (a column called `true_or_false`).
  return str if attribute_method?(str)
  segments = str.split(/_and_|_or_/)
  resolved = segments.map { |segment| resolve_alias_segment(segment) }
  return str if resolved == segments
  combinator = str[/_(or|and)_/, 1] || Constants::OR
  resolved.join("_#{combinator}_")
end

def resolvers

def resolvers
  @resolvers ||= []
end

def sanitize_scope_args(key, args)

def sanitize_scope_args(key, args)
  if Ransack.options[:sanitize_scope_args] && !ransackable_scope_skip_sanitize_args?(key, object)
    cast_scope_args(args)
  else
    args
  end
end

def scope_arity(scope)

def scope_arity(scope)
  @klass.method(scope).arity
end

def searchable_associations(str = ''.freeze)

def searchable_associations(str = ''.freeze)
  associations_for(traverse(str))
end

def searchable_attributes(str = ''.freeze)

def searchable_attributes(str = ''.freeze)
  attributes_for(traverse(str))
end

def searchable_class?(klass)

this; by default any class Ransack has been mixed into qualifies.
Whether a class can be the base of a search. An ORM integration refines
def searchable_class?(klass)
  klass.respond_to?(:ransackable_attributes)
end

def sortable_attributes(str = ''.freeze)

def sortable_attributes(str = ''.freeze)
  sortable_attributes_for(traverse(str))
end

def sortable_attributes_for(klass)

model that defines either one has a sort list of its own.
`ransortable_attributes` defaults to `ransackable_attributes`, so a
def sortable_attributes_for(klass)
  allowlist(klass, :ransortable_attributes, :ransackable_attributes) do
    klass.authorizable_ransackable_attributes
  end
end

def strict_conditions?

ignored when either the global option or this search's own option says so.
Unknown attributes, predicates and combinators raise rather than being
def strict_conditions?
  !Ransack.options[:ignore_unknown_conditions] || ignore_unknown_conditions == false
end

def traverse(str, base = @base)

def traverse(str, base = @base)
  str ||= ''.freeze
  segments = str.split(Constants::UNDERSCORE)
  unless segments.empty?
    remainder = []
    found_assoc = nil
    until found_assoc || segments.empty?
      # Strip the _of_Model_type text from the association name, but hold
      # onto it in klass, for use as the next base
      assoc, klass = unpolymorphize_association(
        segments.join(Constants::UNDERSCORE)
      )
      if found_assoc = get_association(assoc, base)
        base = traverse(
          remainder.join(Constants::UNDERSCORE), klass || found_assoc.klass
        )
      end
      remainder.unshift segments.pop
    end
    unless found_assoc
      raise(UntraversableAssociationError,
            "No association matches #{str}")
    end
  end
  klassify(base)
end

def unpolymorphize_association(str)

def unpolymorphize_association(str)
  if (match = str.match(/_of_([^_]+?)_type$/)) &&
     (klass = polymorphic_class(match.captures.first))
    [match.pre_match, klass]
  else
    [str, nil]
  end
end