はじめに
2025年4月に入社して、気づけばもう12月です。環境の変化もあり1年あっという間でした。
前回の記事では新卒でフルリモート環境がどうかって話をしましたが、今はこの環境に慣れてしまってしっかりと仕事ができています。
今回の記事では8月から新規プロジェクトに参加してRansackを使用してソート機能を実装するにあたり、自分がつまずいたポイントを踏まえて、これから実装する人が最短ルートで進められるようにまとめました。
※本記事ではRansack 4.3.0を使用しています。
この記事でやること
- Ransackとは
- Ransackの導入
- ソート機能の実装
sort_linkを使用した一般的なソート機能の実装def self.ransackable_attributesとはdef self.ransackable_associationsとは
- デフォルトでソートされている属性について
- ソートアイコンの変更について
Ransackとは?
Railsアプリケーションに検索機能やソート機能を実装することができます。
Ransackの導入
Gemfileに下記のコードを追加してbundle installを実行すると導入完了です。
gem 'ransack'
ソート機能の実装
下記のような表があったとします。
この場合、並び順はアカウントが作成された順で表示されています。
@users = User.order(created_at: :desc)

名前順で並び替えをしたければ、~.order(name: :asc)とすれば良いでしょうか。

名前順で並び替えができました。
しかしこれでは並び替えをしたければコードを変更するしかありません。
そのため、一般的なソート機能の動きを実装するため下記のようなソート機能を実装しようと思います。

sort_linkを使用した一般的なソート機能の実装
sort_linkをviewに記述することでクリックするとクリックされた列でソートすることができます。
sort_link(Ransackのオブジェクト, ソート対象のカラム名, 表示するリンクテキスト)のように扱います。
before
<div class="users">
<h1>Users</h1>
<table class="users-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= l(user.created_at, format: :short) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
after
<div class="users">
<h1>Users</h1>
<table class="users-table">
<thead>
<tr>
<th><%= sort_link(@q, :id, "ID") %></th>
<th><%= sort_link(@q, :name, "Name") %></th>
<th><%= sort_link(@q, :email, "Email") %></th>
<th><%= sort_link(@q, :created_at, "Created") %></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= l(user.created_at, format: :short) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
しかしこれではエラーが発生します。

controllerでRansackのオブジェクトを宣言する必要があります。
def index
@q = User.ransack(params[:q])
@users = @q.result.order(created_at: :desc)
end
これでsort_linkを使用して一般的なソート機能を実装することができました。

def self.ransackable_attributesとは
実は先ほどの実装ではページの表示はできますが、リンクをクリックしてソートしようとするとエラーが発生します。

※ここでは検索という言葉で説明していますが、ソートする場合も同様です
要は「Ransackは検索可能として明示的に許可された属性が必要」ということで、モデルの全カラムを検索ができてしまう状態だとセキュリティ的に危険なので、どの属性を検索して良いか指定しないとエラーになってしまいます。
エラー文から分かるように、Userモデルにはid, name, email, created_at以外にも様々な属性が存在しています。そのため、意図しない操作を防ぐためにどの属性を検索可能にするか指定する必要があります。
今回はid, name, email,created_atのみでいいのでmodels/user.rbに下記のコードを追加します。
class User < ApplicationRecord
def self.ransackable_attributes(auth_object = nil)
%w[id name email created_at]
end
end
これで一般的なソート機能が実装できました。


def self.ransackable_associationsとは
もし、このようにincludesされていた場合どうなるでしょうか。
def index
@q = Post.includes(:user).ransack(params[:q])
@posts = @q.result.order(created_at: :desc)
end

先ほどのransackable_attributesの設定はこのようになります。
class Post < ApplicationRecord
def self.ransackable_attributes(auth_object = nil)
%w[id content created_at]
end
end
これで、ID, Content, Createdのソート機能は実装できました。
しかし、Userはどうでしょうか。先ほどのransackable_attributesにはnameを追加していません。
もちろん追加してもPostモデルにはnameという属性は存在しないため意味はありません。
関連がある場合Ransackのソートはややこしくなります。
もしこの状態でUserのソートをしようとするとこのようにエラーが発生します。

Postモデルでassociationを検索条件として使いたいなら、どのassociationを検索に使っていいのかRansackに明示する必要があるってことです。
今回の場合だと、Userモデルを検索条件として使いたいので、ransackable_associationsにUserを指定する必要があります。
class Post < ApplicationRecord
def self.ransackable_associations(auth_object = nil)
%w[user]
end
end
デフォルトでソートされている属性について
Ransackではデフォルトでソートされる属性を指定することができます。
.order()と似ていますが、Ransackで用意されている方法を使うと何がいいのか、それはページにアクセスしたときソートされている属性に▲▼を表示することができます。
アクセスした時に▲▼が表示されていることで、ユーザに初期状態ではどの項目がソートされているのかを知らせることができます。.orderだとこれができません。
def index
@q = User.ransack(params[:q])
@q.sorts = 'created_at desc' if @q.sorts.empty?
@users = @q.result
end

以上で「Ransackでソート機能を実装して学んだこと」について終わります。
2025年もあと少しです。来年も健康に気をつけて全力で人生を楽しみつつ仕事もこなしていこうと思いいます。ではではノシ
