# How to Use ActiveRecord Scopes with Ransack Searches: A Complete Guide

> Master ActiveRecord scopes with Ransack searches. Learn how to whitelist scopes for powerful, dynamic querying and enhance your Rails application's search functionality.

- Repository: [ActiveRecord Hackery/ransack](https://github.com/activerecord-hackery/ransack)
- Tags: tutorial
- Published: 2026-02-23

---

**To use ActiveRecord scopes with Ransack searches, you must explicitly whitelist them by defining the `ransackable_scopes` class method in your model to return an array of scope symbols, enabling `Context#chain_scope` to apply them to the relation before predicate processing.**

Effectively using ActiveRecord scopes with Ransack searches in the `activerecord-hackery/ransack` library requires implementing a whitelist security pattern. By default, Ransack returns an empty array from `ransackable_scopes` and will not execute any scopes, so you must explicitly expose the specific filtering logic you want accessible through the search interface.

## Understanding the Whitelist Mechanism

Ransack prevents arbitrary scope execution through a strict whitelist system defined in [`lib/ransack/adapters/active_record/base.rb`](https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/adapters/active_record/base.rb) (lines 57‑63). The base implementation returns an empty array:

```ruby
def ransackable_scopes(auth_object = nil)
  []
end

```

To expose scopes, override this method in your model and return an array of symbols representing the scope names you want searchable. Ransack will only execute scopes present in this whitelist when processing search parameters.

## How Ransack Processes Scopes Internally

When you call `Model.ransack(params)`, the library performs a specific sequence of operations defined in [`lib/ransack/context.rb`](https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/context.rb) to validate and apply scopes:

1. **Whitelist verification**: `Context#ransackable_scope?(key, klass)` (lines 70‑72) checks if the search parameter key exists in the array returned by `ransackable_scopes`.
2. **Scope chaining**: If verified, `Context#chain_scope(scope, args)` (lines 76‑86) inspects the scope's arity and applies it to the underlying `ActiveRecord::Relation`.
3. **Argument forwarding**: The `chain_scope` method handles boolean values, single values, or splatted arguments based on the scope's defined parameters.
4. **Predicate processing**: After applying scopes, Ransack processes standard search predicates on the modified relation.

## Implementing Searchable Scopes in Your Models

### Basic Scope Whitelisting

Define your scopes and whitelist them by overriding `self.ransackable_scopes` in your model:

```ruby

# app/models/employee.rb

class Employee < ApplicationRecord
  scope :activated, ->(boolean = true) { where(active: boolean) }
  scope :salary_gt, ->(amount) { where('salary > ?', amount) }
  
  def self.hired_since(date)
    where('start_date >= ?', date)
  end

  def self.ransackable_scopes(auth_object = nil)
    %i[activated hired_since salary_gt]
  end
end

```

### Role-Based Access with auth_object

The optional `auth_object` parameter enables dynamic whitelisting based on the current user. Ransack passes this object from the controller to the model, allowing you to expose different scopes to different user roles:

```ruby
def self.ransackable_scopes(auth_object = nil)
  if auth_object&.admin?
    %i[activated hired_since salary_gt]
  else
    %i[activated]
  end
end

```

In your controller, pass the user object as the `auth_object`:

```ruby
@q = Employee.ransack(params[:q], auth_object: current_user)

```

### Handling Boolean and Complex Arguments

Ransack automatically sanitizes certain argument types before passing them to scopes:

- **Boolean scopes**: Triggered by true-ish values (`true`, `'true'`, or `['true']`)
- **Array arguments**: Must be wrapped in an additional array (e.g., `salary_gt: [[100_000]]`) to prevent Ransack from flattening them
- **Single values**: Passed directly as strings or numbers

When a scope accepts arguments, Ransack's `chain_scope` method inspects the method arity to determine how to forward the supplied values from the query hash.

## Customizing Argument Sanitization

By default, Ransack converts string representations of booleans (`'true'`/`'false'`) to actual boolean values before passing them to scopes. If you need to receive the raw string values unchanged, implement `ransackable_scopes_skip_sanitize_args` in your model:

```ruby
def self.ransackable_scopes_skip_sanitize_args
  [:my_raw_scope]
end

```

This prevents Ransack's automatic boolean conversion for the specified scopes, ensuring the scope receives the exact parameter value submitted in the form.

## Complete Implementation Example

### Model Definition

```ruby

# app/models/article.rb

class Article < ApplicationRecord
  has_many :comments
  
  scope :with_long_comments, -> { joins(:comments).where('comments.body LIKE ?', '%---%') }
  scope :published_since, ->(date) { where('published_at >= ?', date) }
  
  def self.ransackable_scopes(_auth_object = nil)
    [:with_long_comments, :published_since]
  end
  
  def self.ransackable_scopes_skip_sanitize_args
    []
  end
end

```

### Controller Implementation

```ruby
class ArticlesController < ApplicationController
  def index
    @q = Article.ransack(params[:q], auth_object: current_user)
    @articles = @q.result(distinct: true).page(params[:page])
  end
end

```

### Search Form View

```erb
<%= search_form_for @q, url: articles_path, method: :get do |f| %>
  <%= f.check_box :with_long_comments, {}, true, false %> 
  <%= f.label :with_long_comments, "With Long Comments" %>
  
  <%= f.label :published_since, "Published Since" %>
  <%= f.date_field :published_since %>
  
  <%= f.submit "Search" %>
<% end %>

```

When the form submits with `published_since: "2024-01-01"`, Ransack calls `Article.published_since('2024-01-01')` after verifying it against the whitelist in `ransackable_scopes`.

## Summary

- **Whitelist required**: Scopes must be explicitly returned by `ransackable_scopes` in [`lib/ransack/adapters/active_record/base.rb`](https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/adapters/active_record/base.rb) or they will be ignored.
- **Security through auth_object**: Pass user context to dynamically control scope availability based on roles or permissions.
- **Argument handling**: Boolean scopes activate on true-ish values; array arguments require double-wrapping (`[[value]]`).
- **Internal processing**: `Context#chain_scope` in [`lib/ransack/context.rb`](https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/context.rb) handles arity inspection and argument forwarding.
- **Sanitization control**: Use `ransackable_scopes_skip_sanitize_args` to bypass automatic boolean conversion when raw values are required.

## Frequently Asked Questions

### Why isn't my ActiveRecord scope executing with Ransack?

Your scope is likely not included in the `ransackable_scopes` whitelist. By default, this method returns an empty array, preventing any scope execution for security. Override `self.ransackable_scopes` in your model to return an array containing your scope name as a symbol, such as `%i[my_scope_name]`.

### How do I pass an array argument to a Ransack scope?

When a scope expects an array argument, you must wrap the array in an additional array in your search parameters. For example, if your scope is `scope :by_ids, ->(ids) { where(id: ids) }`, pass `by_ids: [[1, 2, 3]]` in the query hash rather than `by_ids: [1, 2, 3]`. This prevents Ransack from flattening the array during parameter processing.

### Can I restrict specific scopes to admin users only?

Yes, use the `auth_object` parameter passed to `ransackable_scopes(auth_object)`. In your controller, pass the current user as the auth_object (`Model.ransack(params, auth_object: current_user)`), then implement conditional logic in the method to return different scope arrays based on the user's role or permissions.

### How do I prevent Ransack from converting my scope arguments to booleans?

If your scope needs to receive the literal string `'true'` or `'false'` rather than boolean `true`/`false`, define `ransackable_scopes_skip_sanitize_args` in your model and return an array containing the scope name. This skips Ransack's automatic boolean sanitization for those specific scopes while maintaining it for others.