Ruby and Rails glossary

Ransack

Ransack is a Ruby gem that adds searching and sorting to Rails apps. It turns request params like name_cont=ann into Active Record queries, with form and sort-link helpers for your views, and needs no search server.

Ransack is a Ruby gem that adds searching and sorting to a Rails app by turning request parameters into Active Record queries. A param named email_cont becomes WHERE email LIKE '%value%', a param named s becomes an ORDER BY, and the gem ships view helpers that build the matching form and the clickable column headers. It runs inside your database: no search server, no index to keep in sync. Ransack works on your models and the joins between them, which makes it a common choice for admin screens, filter bars and internal tools.

The current release is Ransack 6.1.0, published in November 2026, and it requires Rails 8.1 and Ruby 3.3 or later (checked in November 2026). Apps on Rails 8.0 or older stay on the 6.0 line.

The search object

Everything starts with ransack, a class method Ransack adds to every model. You pass it a hash of conditions, usually params[:q], and get back a search object. Calling result on that object returns an ordinary relation, so you can chain includes, pagination or scopes onto it.

# Gemfile
gem "ransack"
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def index
    @q = Order.ransack(params[:q])
    @orders = @q.result.includes(:customer).order(created_at: :desc)
  end
end

The search object also remembers what was searched, which is how the form re-fills itself and how sort links know the current direction.

Predicates

Each condition key is an attribute name followed by a predicate. The predicate decides the SQL operator. The ones you reach for most often:

  • _eq and _not_eq: equal and not equal (status_eq=paid).
  • _cont and _not_cont: contains, a LIKE '%value%' match.
  • _i_cont: contains, ignoring case on every database.
  • _start and _end: starts with and ends with.
  • _gt, _gteq, _lt, _lteq: comparisons for numbers and dates.
  • _in and _not_in: match any or none of a list of values.
  • _null, _not_null, _present, _blank: empty checks.
  • _true and _false: boolean columns.
  • _matches: a raw LIKE pattern you write yourself.

You can join several attributes with _or_ (or _and_) to search them with one value, and add _any or _all to many predicates to pass several values.

Order.ransack(
  status_in: ["paid", "shipped"],
  total_gteq: 100,
  created_at_gteq: 30.days.ago,
  number_or_notes_i_cont: "refund"
).result

Two details changed recently and trip up older code. Since Ransack 5.0, % and _ typed into a cont, start or end search are matched as literal characters on every database; use _matches when you want a pattern. Since 6.0, plain cont on PostgreSQL is case-sensitive like PostgreSQL's own LIKE, so use _i_cont where users expect case not to matter.

search_form_for and sort_link

search_form_for wraps Rails' form builder around the search object. Field names are the condition keys, and the form submits them under q with a GET request. sort_link renders a column header that toggles the sort and shows an arrow for the current direction.

<%= search_form_for @q do |f| %>
  <%= f.label :number_or_notes_i_cont, "Order number or notes" %>
  <%= f.search_field :number_or_notes_i_cont %>

  <%= f.label :total_gteq, "Minimum total" %>
  <%= f.number_field :total_gteq %>

  <%= f.submit "Search" %>
<% end %>

<table>
  <tr>
    <th><%= sort_link(@q, :number) %></th>
    <th><%= sort_link(@q, :total, "Amount", default_order: :desc) %></th>
    <th><%= sort_link(@q, :customer_name, "Customer") %></th>
  </tr>
</table>

Ransack also has turbo_search_form_for for apps that want the search to submit through Turbo, and sort_url when you need the URL without the link. If your views use sort_link or sort_url, run 6.0.1, 5.0.3, 4.4.4 or later: those October 2026 releases fixed a flaw where a crafted query string could make a sort link run script or point to another site.

Searching across associations

To reach into an association, prefix the attribute with the association name. customer_name_cont searches the name column of an order's customer, and line_items_sku_eq searches its line items. Ransack adds the join for you.

class Order < ApplicationRecord
  belongs_to :customer
  has_many :line_items

  def self.ransackable_attributes(auth_object = nil)
    %w[number status total notes created_at]
  end

  def self.ransackable_associations(auth_object = nil)
    %w[customer line_items]
  end
end

class Customer < ApplicationRecord
  has_many :orders

  def self.ransackable_attributes(auth_object = nil)
    %w[name email]
  end
end
Order.ransack(customer_email_cont: "@acme.com", line_items_sku_eq: "TSHIRT-M")
  .result(distinct: true)

Two things to know. Every model on the path is asked for its own allowlist, so the association must be listed on Order and the column must be listed on Customer. And a has_many join can return the same order once per matching line item, which is why the example asks for result(distinct: true). More on how Rails wires these up in associations.

The Ransack 4 allowlist, and why it exists

Up to version 3, Ransack let a search touch every column and every association on a model. In January 2023 the security firm Positive Security published research showing what that meant for any page that passed params[:q] straight to ransack. An attacker could follow associations to a sensitive column and guess it one character at a time with _start. On a blog's post index, a request like ?q[user_reset_password_token_start]=2 returns posts only if some author's reset token starts with 2. Repeat for each character and the full token comes out, which is enough to take over the account. The same trick works on password hashes, API keys and any other column a model can reach.

Ransack 4.0.0, released on 9 February 2023, changed the default. Nothing is searchable until the model says so. Calling ransack on a model with no ransackable_attributes raises an error that begins "Ransack needs Order attributes explicitly allowlisted as searchable", and keys outside the list are ignored.

The four methods you can define:

  • ransackable_attributes: columns and ransackers that can be searched.
  • ransackable_associations: associations a search can follow.
  • ransortable_attributes: what can be sorted. Defaults to the searchable attributes.
  • ransackable_scopes: model scopes a search can call. Empty by default.

Each takes an optional auth_object, so the list can depend on who is asking.

class User < ApplicationRecord
  def self.ransackable_attributes(auth_object = nil)
    if auth_object == :admin
      %w[name email role created_at last_sign_in_at]
    else
      %w[name]
    end
  end
end
@q = User.ransack(params[:q], auth_object: current_user.admin? ? :admin : nil)

If you are upgrading an old app, Ransack offers authorizable_ransackable_attributes, which returns every column. It is a convenient base for an exclusion list (authorizable_ransackable_attributes - %w[encrypted_password reset_password_token]), but returning it unfiltered from ApplicationRecord restores the pre-4.0 exposure.

Since 6.0 there is a second option. A model that defines none of the lists can be searched from permitted strong parameters, so the controller's permit call becomes the allowlist. A plain hash, or unpermitted params, still needs the model lists. A model that defines a list always applies it on top of the controller's.

Using Ransack in an Avo app

Your team can put Ransack behind its admin with Avo. With the Dynamic Filters add-on, you mark a field filterable: true and the filter bar's queries run through Ransack, so each filterable attribute goes in the model's ransackable_attributes list, the same allowlist you keep for your own controllers. For search, each resource's self.search holds a query, and Ransack is the default the Avo docs show, not a requirement. The Global Search add-on runs that same query from a Cmd+K palette across every resource.

Frequently asked questions

What is Ransack in Rails?

Ransack is a gem that adds search and sort to Rails apps by translating params such as title_cont or price_gteq into Active Record queries. It includes search_form_for and sort_link view helpers and runs entirely in your database, with no separate search server.

Why does Ransack 4 require ransackable_attributes?

Before 4.0, any column and any association was searchable by default, and researchers showed attackers could guess secrets such as password reset tokens one character at a time through search params. Ransack 4.0 made searching opt-in, so you now list the searchable attributes and associations on each model.

Is it safe to pass params[:q] to Ransack?

It is safe when the allowlists only contain columns you would be happy to show users, because a search on a column leaks its contents one character at a time. Keep secrets such as tokens, password hashes and API keys out of ransackable_attributes on every model a search can reach, and stay on a patched release.

How do I search across associations with Ransack?

Prefix the attribute with the association name, as in customer_name_cont or line_items_sku_eq. The association must be in the parent model's ransackable_associations and the column in the associated model's ransackable_attributes. Add distinct: true to result when a has_many join could repeat rows.

Is Ransack a full-text search engine?

No. It builds SQL conditions such as LIKE, equality and comparisons on your existing tables, which suits filtering and admin search. For ranked full-text search, use your database's full-text features (for example through pg_search) or a dedicated search service.

Try Avo for free