This page is for anyone reading or changing Ransack’s source. It describes the pieces, where the boundary with Active Record is, and which parts of Active Record Ransack reaches into.

The pieces

A search goes through four stages. Each has a home under lib/ransack/.

Stage Where What it does
Parse search.rb, nodes/ Turns the params hash into a tree of Grouping, Condition, Attribute, Value and Sort nodes. Nothing here knows about the database.
Resolve context.rb, active_record/context.rb Maps attribute names like articles_title onto tables, columns and the joins needed to reach them.
Build visitor.rb, predicate.rb, constants.rb Walks the node tree and produces Arel predicates and orderings.
Integrate active_record/ Everything that touches Active Record: the class methods added to models, the join machinery, and the Context that evaluates the search into a relation.

Around those sit configuration.rb (global options and the predicate registry), translate.rb and locale/ (i18n), helpers/ (the form builder and view helpers, loaded only when Action Controller is) and ransacker.rb (custom attributes).

The Active Record boundary

Ransack::Context is the seam. The generic class holds what every ORM would need: binding attribute names to nodes, walking association paths, checking the ransackable_* allowlists and chaining scopes. Everything that needs a real database sits in the subclass Ransack::ActiveRecord::Context.

A context is chosen through a registry rather than a hard-coded check, so an integration for another ORM can register its own:

Ransack::Context.register do |object, options|
  case object
  when Class
    MyOrm::Context.new(object, options) if object < MyOrm::Document
  when MyOrm::Criteria
    MyOrm::Context.new(object, options)
  end
end

The resolver receives whatever was passed to ransack, a model class or a query object, and returns a context or nil. Ransack’s own Active Record resolver is registered in lib/ransack/active_record/context.rb. A context subclass implements relation_for, type_for, evaluate, attribute_method?, table_for, klassify and the join-building methods; the Active Record context is the reference implementation.

What Ransack takes from Active Record’s internals

Active Record’s public query interface cannot express two things Ransack needs: an outer join that is added one association at a time on top of a relation that already has joins, and a join through a polymorphic belongs_to to one named class. To get them, lib/ransack/active_record/ prepends small modules onto three internal classes:

Active Record class Ransack module Why
Associations::JoinDependency JoinDependencyExtensions Accept Ransack::ActiveRecord::Join nodes in the association tree, and hand back the aliased tables for an association so a later correlated subquery can reuse them.
Associations::JoinDependency::JoinAssociation JoinAssociationExtensions Carry the join type and the polymorphic target class; build join constraints against tables Ransack already aliased.
Reflection::AbstractReflection ReflectionExtensions Add the type = 'ClassName' constraint when a polymorphic association is joined to one class.

Before 6.0 these lived in a separate top-level Polyamorous namespace, a remnant of the gem of that name that was merged into Ransack in 2019.

These are private APIs. They are the part of Ransack most likely to need attention when a new Rails version ships, which is why the nightly CI job runs the suite against Rails main. Keeping this list short, and moving items off it as Active Record grows public equivalents, is an explicit goal.

Everything else goes through public API: reflect_on_all_associations, columns_hash, type_for_attribute, attribute_aliases, defined_enums, arel_table, the relation query methods, and the connection pool’s schema cache for column types.

Databases

Ransack does not have per-database code paths. Arel renders the query for whichever database is connected, and the two decisions that do depend on the database — whether a case-insensitive LIKE needs LOWER() and which function counts characters — are answered by Ransack::ActiveRecord::Dialect. A dialect is derived from the adapter class’s ancestry (so an adapter built on the PostgreSQL adapter is PostgreSQL) or set with config.dialect, and is the only place a database is named in lib/. See Configuration.