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
Alice Johnsonalice@example.com4 months ago
Bob Smithbob@example.com4 months ago
Carol Williamscarol@example.com4 months ago
David Browndavid@example.com4 months ago
Eve Daviseve@example.com4 months ago

Posts

Posts
Title, sorted descendingCreated
Understanding Active Record4 months ago
Turbo Frames in practice4 months ago
Testing best practices4 months ago
Stimulus controllers4 months ago
Search with Ransack4 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.