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
Paul Robinsonpaul@example.comabout 2 months ago
Quinn Harrisquinn@example.comabout 2 months ago
Ruby Clarkruby@example.comabout 2 months ago
Sam Lewissam@example.comabout 2 months ago
Tina Walkertina@example.comabout 2 months ago

Posts

Posts
Title, sorted descendingCreated
Pagination with Pagyabout 2 months ago
Importmap and modern JSabout 2 months ago
Getting started with Railsabout 2 months ago
Deploying to productionabout 2 months ago
CSS architecture patternsabout 2 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.