【Rails】ソート機能を作ってみる

今回はindex画面にソート機能をつけてみたいと思います!

完成予想図はこんな感じです。

gyazo.com

実装にあたり、解らないところが噴出しましたのでそちらも一つ一つ調べて行きます。

目次

1.前提条件

1-1.モデル

先ずはモデルですね。こんなテーブルを用意しました。

class CreateStocks < ActiveRecord::Migration[6.0]
def change
create_table :stocks do |t|
 
t.string :publisher, comment: '出版社名'
t.string :magazine_name, comment: '雑誌名'
t.integer :num, comment: '冊数'
t.integer :price, comment: '本体価格'
t.string :i_form, comment: '発行形態'
t.string :purchased, comment: '買切雑誌'
t.references :calendar, null: false, foreign_key: true
t.timestamps
end
end
end

1-2.コントローラー

class StocksController < ApplicationController

def index
@calendar = Calendar.find(params[:calendar_id])
@stocks = @calendar.stocks
end

end

1-3.ビュー

<table>
<thead>
<tr>
<th>陳列</th>
<th>出版社名</th>
<th>雑誌名</th>
<th>冊数</th>
<th>本体価格</th>
<th>発行形態</th>
<th>買切雑誌</th>
 
</tr>
</thead>
<tbody>
<% @stocks.each do |stock| %>
<% if stock.num > 0 %>
 
 
<tr>
<td><%= stock.display %></td>
<td><%= stock.publisher %></td>
<td><%= stock.magazine_name %></td>
<td><%= stock.num %></td>
<td><%= stock.price.to_s(:delimited) %></td>
<td><%= stock.i_form %></td>
<td><%= stock.purchased %></td>
 
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>

特出した内容ではありません。

今回の実装に関係ないところは省略しています。

さあ実装です!

2.コントローラー

先ずはコントローラーに必要な処理を記述して行きます。

完成形はこんな感じです。

class StocksController < ApplicationController
# ここを追記
helper_method :sort_column, :sort_direction

def index
@calendar = Calendar.find(params[:calendar_id])
# order以降を追記
@stocks = @calendar.stocks.order("#{sort_column} #{sort_direction}")
end
# private以下を追記
private

def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
end

def sort_column
Stock.column_names.include?(params[:sort]) ? params[:sort] : 'id'
end

end

helper_method :sort_column, :sort_direction

privateメソッドとして定義している「sort_direction」と「sort_column」を

viewからも使えるようにするため、helper_methodに追加しています。

sort_directionメソッド

「%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'」

と言う記述が、もう訳がわからんのですが、一つずつ調べてみました。

%w[asc desc]

asc(:昇順)desc(:降順)で、ここは解るのですが、%wがなんじゃらほい。

どうやら、「%記法」と言うものらしいです。

%記法とは

%記法の本来の目的は、

文字列に含まれる「’ ‘」や「” “」を「( )」などの非英数字に置き換えて、

「’ ‘」や「” “」に対するエスケープを省略するために利用するのだそうです。

例えば、次の構文は全て同じ処理結果になります。

  • “\“abc\””
  • %(“abc”)
  • %!”abc”!
  • %?”abc”?
  • %+”abc”+

可読性をよくすることは、バグ発生の減少に繋がります!

そして、%wや%Wは配列の作成に特化してコーディングが楽になるように設計された、

%記法の延長上にあるRuby構文でした!

%w記法(配列)

%w記法は、文字列からなる配列を作成したいときに

「[ ]」(ブラケット)や「" "」(ダブルクォーテーション)を省略して記述するための

Rubyの構文です。

%w(文字列1 文字列2 文字列3...)

こんな感じで記述します。

文字列間の空白が区切る意味になります。

%w記法を使うと、プログラムコードの可読性がよくなって、

記述もカンタンになるので、よく利用される書き方なのだそうです!

%w記法を使わない場合と使う場合のコードの違いを比較して、

どのように可読性がよくなるかを確認して見ましょう。

%w記法を使わない場合(スクールで習っていた内容):

puts ["Qiita", "ブログ", "Ruby", "\"%w記法\""]

%w記法を使う場合:

puts %w(Qiita ブログ Ruby "%w記法")

だいぶわかりやすい!特に%w記法のところ!

実行結果はどちらも同じでした。

Qiita
ブログ
"%w記法"

と、言う事で、

%w[asc desc]は、『asc』と『desc』と言う文字の配列と言う意味でした。

%wと%Wの違い

これは余談ですが、%wと%Wがあるらしく、

こちらの違いがちょっと面白かったので記載します。

%W記法は、%wと同様で空白区切りの文字列から配列を作成できますが

  • %W → 式展開あり
  • %w → 式展開なし

チョットナニイッテルカワカラナイ・・・ぶ、VSCode君お願い・・・。

%Wの場合は

blog = "ブログ"
puts %W(Qiita #{blog})

結果は・・・

Qiita
ブログ

%wだと

blog = "ブログ"
puts %w(Qiita #{blog})

結果は・・・

Qiita
#{blog}

ふむふむ。ちゃんと代入した結果を表示してくれるって訳ですね!便利!!

include?(params[:direction])

「配列.include?(値)」で、配列の中に値と同じものがあった場合

「true」を返します。

例題:

a = [ "a", "b", "c" ]
puts a.include?("b")
puts a.include?("z")

結果は

true
false

と言うわけで、『%w[asc desc].include?(params[:direction])』の記述は

パラムスの中に入っている「direction」がasc かdescのどちらか調べてね。

と言う事ですね。

「direction」が何を指すのかは後述します。

? params[:direction] : 'asc'

この、『?』から記述を三項演算子と言うのだそうです。

三項演算子

条件分岐を行うための演算子って事なのですがぶっちゃけ良くわからない・・・。

条件式 ? 真の時の値 : 偽の時の値

条件式を判定して、

trueだった時は「真の時の値」を、

alseだった時は「偽の時の値」を返します。
条件や返り値がシンプルな場合、if分よりも簡潔に記述できる優れもの!

(結論)sort_directionメソッドの意味

上記の長々とした解説の内容は

パラムスの中に入っている「direction」がasc かdescのどちらか調べて

ascかdescだったらそのままその値を、どちらでもない時はascを返してね。

もっと言うとページへ遷移した直後はdirectionの中は何も決まっていないので

初期値はasc(昇順)にしてね

と言う意味でした。説明長いな!!

 sort_columnメソッドの解説

Stock.column_names.include?(params[:sort]) ? params[:sort] : 'id'

こちらも条件式と三行演算子の組み合わせです。

三行演算子は上記をご確認いただくとして「column_names」って何やねん。

column_names

column_namesは「モデル名.column_names」と記述する事で

モデルで設定されているカラム名を調べてくれるメソッドです。

試しに、VSCode君で確認してみます。

rails runner 'puts Stock.column_names'

結果は

Running via Spring preloader in process 90223
id
 
display
publisher
magazine_name
num
price
 
i_form
 
purchased
calendar_id
created_at
updated_at

ちなみに、

rails runner 'p Stock.column_names'

と記述すると

Running via Spring preloader in process 90259
["id",〜〜省略〜〜 , "updated_at"]

と言った様な配列の記述で返してくれますのでお好みでどうぞ。

rails runner

おまけですが、rails runnerは任意のrubyコードが実行できるコマンドです。

Active Recordも使えるのでbatch処理のときに使えます

(結論)sort_columnメソッドの意味

Stock.column_names.include?(params[:sort])と言う条件式で

パラムスの中にある「sort」が、stockモデル内のカラム名と一致するかを判別して

一致するものがあればそのままsortを返して、なければ「id」を返す。

ページへ遷移した直後は「sort」の中は何も決まっていないので

初期値はid(Stockモデルのid)にしてね

と言う意味でした。

indexメソッドの解説

indexメソッドは

@stocks = @calendar.stocks.order("#{sort_column} #{sort_direction}")

と、「.stocks.order("#{sort_column} #{sort_direction}")」を追記しています。

例えば、ソートが作成された時期の昇順であれば

@stocks = @calendar.stocks.order("created_at asc")

と表記します。こちらの応用ですね。

  • ソートするカラムは#{sort_column}で、ページ遷移直後(初期値)は「id」
  • 順序は#{sort_direction}で、ページ遷移直後(初期値)は「asc」

と、なります。

ふう〜、コントローラーだけでわからない事だらけです。

3.ヘルパーメソッド

お次は毎度勉強になるヘルパーメソッドです。

module StocksHelper
def sort_order(column, title)
direction = (column == sort_column && sort_direction == 'asc') ? 'desc' : 'asc'
link_to title, { sort: column, direction: direction }
end
end

 上記の様に記述します。

こちらもわからないことがあったので一つずつ調べて行きます。

sort_orderメソッド

まず、sort_oderメソッドは二つ引数を指定しています。

  • 第1引数(column)は並び替え元となるデータベース上のテーブルのカラム名
  • 第2引数(title)はViewに表示するカラム名です。
directionの説明

sort_directionメソッドで出てきた「direction」ですね。

三行演算子で返ってきた値が「direction」です。返す値の定義は

「(column == sort_column && sort_direction == 'asc') ? 'desc' : 'asc'」

で、

モデルのカラム名とsort_colimnの値が同じで

かつ(&&)、sort_directionの値がascだった時は「desc」を返してね。

違う時は「asc」を返してね。

要は、

そのカラムのasc(昇順)が選ばれたらdesc(降順)に。

desc(降順)が選ばれたらasc(昇順)にしてね。と言うことになります。

こちらが下の行に記述している「link_to」 にかかってきます。

link_toは、次にクリックされるときのソートの方向をパラメータに設定した

リンクを作るため、上記の様な逆のdirectionになっています。

4.ビューを編集

Helperに作ったsort_orderを<th>に追加します。

長くなりそうなので、修正を行った<thead>の部分のみです。

<thead>
<tr>
<th><%= sort_order "display", "陳列" %></th>
<th><%= sort_order "publisher", "出版社名" %></th>
<th><%= sort_order "magazine_name", "雑誌名" %></th>
<th><%= sort_order "num", "冊数" %></th>
<th><%= sort_order "price", "本体価格" %></th>
<th><%= sort_order "i_form", "発行形態" %></th>
<th><%= sort_order "purchased", "買切雑誌" %></th>
 
</tr>
</thead>

表示されている各カラムでソートできる様にしてみました。

やった!出来た〜!!

5.課題は山積みと感謝

お疲れ様でした!

初めての事ばかりなので、調べては試しにやってみるの繰り返し

ブログに書くからには出来るだけ分かりやすく丁寧にとも思い

そこでさらに調べる。結果さらに遅くなるを繰り返しています(笑)

これで終わりではなくて、課題は山積み。最低でも

  • まだ実装していない検索結果機能を結び付けたい
  • 何でソートしているのか、色をつけるなりして可視化したい

こんな機能はつけたいな〜と考えています。

てか、ブログもきちんと正確に、かつ読みやすく試行錯誤してますが

実際どうなのだろう・・・。

ともあれ、先人の方達の遺産を拝見しながら

少しづつでもステップアップして行きたいなと考えております!

ありがとうございます!!

統合ターミナルありがとう!

毎度のことなのですが、ちゃんと動くか下調べって大事ですよね。

そんな時に大活躍するのがVSCode君の統合ターミナル様です!

control + shift(Macでは^マーク) + `で降臨いただけます。 

教えてくれたスクールのチームメイトさんに大感謝です!ありがとー!

参考にさせて頂いたサイト様

【Ruby入門】%記法の使い方まとめ|%w・%W・%q・%r・%i・%I… | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

Array#include? (Ruby 2.7.0 リファレンスマニュアル)

[Rails] ソート機能をつくる|ota|note

感謝!!