Skip to main content

Ransack is fully supported but entirely optional. The engine works without any search system.

Installation

Add Ransack to your application:

bundle add ransack

Ransack 4+ requires you to allowlist searchable attributes on your models:

class User < ApplicationRecord
  def self.ransackable_attributes(_auth_object = nil)
    %w[name email created_at]
  end

  def self.ransackable_associations(_auth_object = nil)
    []
  end
end

Search and sort helpers

The engine provides helpers that wrap Ransack with styled controls. If Ransack is not installed, both helpers degrade gracefully:

Available helpers
Helper Purpose
l_ui_search_form Styled search form wrapping Ransack's search_form_for
l_ui_sort_link Styled sort link wrapping Ransack's sort_link

Search and sort example

Two independent collections on one page, each in a Turbo Frame. Searching or sorting one does not affect the other. The URL updates for back/forward navigation.

Users

Users
NameEmailJoined
Karen Whitekaren@example.com4 months ago
Leo Martinezleo@example.com4 months ago
Mia Andersonmia@example.com4 months ago
Noah Thomasnoah@example.com4 months ago
Olivia Garciaolivia@example.com4 months ago

Posts

Posts
TitleCreated, sorted ascending
Testing best practices4 months ago
Background jobs with Solid Queue4 months ago
Authentication with Devise4 months ago
Search with Ransack4 months ago
Pagination with Pagy4 months ago

Usage

In your controller, use Ransack's search_key to namespace each collection:

def index
  @users_q = User.ransack(params[:users_q], search_key: :users_q)
  @users   = @users_q.result(distinct: true)

  @posts_q = Post.ransack(params[:posts_q], search_key: :posts_q)
  @posts   = @posts_q.result(distinct: true)
end

In your view, wrap each collection in a turbo_frame_tag and pass turbo_frame: to the helpers. Add the l-ui--search-form controller with rewriteLink on the frame to preserve URL params in pagination links:

<%= turbo_frame_tag "users_collection", data: { turbo_action: "advance",
      controller: "l-ui--search-form", l_ui__search_form_scope_value: "users_q",
      action: "click->l-ui--search-form#rewriteLink" } do %>
  <%= l_ui_search_form(@users_q, url: users_path, fields: [:name, :email],
                       clear: true, turbo_frame: "users_collection") %>
  <%= l_ui_table(@users,
      columns: [
        { attribute: :name, primary: true, render: ->(u) { u.name } },
        { attribute: :email, render: ->(u) { u.email } }
      ],
      caption: "Users",
      query: @users_q,
      url: users_path,
      turbo_frame: "users_collection") %>
  <%= l_ui_pagy(@users_pagy) %>
<% end %>

The search_key flows through automatically - l_ui_search_form reads it from the query object and passes it as as: to search_form_for. The clear button preserves other collections' params. data-turbo-action="advance" pushes state to the URL for back/forward navigation. The rewriteLink action merges current URL params into pagination links so other collections' page positions are preserved.

Sort link options

<!-- Basic sort link -->
<%= l_ui_sort_link(@q, :name) %>

<!-- Custom label -->
<%= l_ui_sort_link(@q, :created_at, "Joined") %>

<!-- Default descending order -->
<%= l_ui_sort_link(@q, :created_at, "Joined", default_order: :desc) %>

<!-- With Turbo Frame -->
<%= l_ui_sort_link(@q, :name, turbo_frame: "my_frame") %>

<!-- With HTML options -->
<%= l_ui_sort_link(@q, :name, html: { data: { turbo_action: "replace" } }) %>

See the search page for the full search helper API, options, and partials reference.