第5回 Ruby on Rails〜タスク管理アプリケーション 機能追加④ (データの絞り込み・ソート、その他)〜

Ruby

はじめに

今回はタスク管理アプリにデータの検索・ソート機能を追加します。

また、その他にscopeやフィルタの追加やURLを自動でリンクとして表示できるように変更します。

データの絞り込み

Railsにはデータベースからデータを検索するための機能群が用意されています。
また、更新・削除時に更新対象の条件の絞り込みも同じやり方を使えます。

データを絞り込んで検索を行う場合、以下の3点からコードを組み立てます。
絞り込み条件は付けない場合もあります。

  1. 起点
  2. 絞り込み条件
  3. 実行部分

以下のコードではadminカラムにtrueが入っているUserのうち最初に見つかったものを取得します。

User.where(admin:true).first

このコードを上記の3つの部分に分解すると以下のようになります。

  • User…①起点
  • where(admin:true)…②絞り込み条件
  • first…③実行部分

絞り込みの起点

検索・更新のコードを書き出すスタート地点です。基本的には処理対象のモデルのクラスが起点となります。
user.tasksのようなhas_many関連を起点にすることもできます。この場合、Taskを起点として絞り込むのと同じ効果となります。

絞り込み条件

起点に対して絞り込みの条件を追加する部分です。以下のようなクエリー用のメソッドを利用できます。クエリー用のメソッドは重ねがけできます。

クエリー用のメソッドはそれを読んだ時点では、まだ検索などの処理は実行されません。実行部分にあたるメソッドを読んで初めて実行されます。

実行部分

実行部分として利用できるメソッドは以下のようなものがあります。

データのソート

実際にタスク管理アプリのタスク一覧を作成日の新しい順に表示できるようにします。

作成日時の新しい順で検索するには、ORDER BY created_at DESCというようなORDER BY節をSQLにつける必要があります。

TasksController(app/controllers/tasks_controller.rb)のindexアクションを以下の通りに変更します。

app/controllers/tasks_controller.rb

class TasksController < ApplicationController
  def index
    @tasks = current_user.tasks.order(created_at: :desc);
  end
  ・・・
end

scopeの活用

scopeというメソッドを用いることで、クエリー用のメソッドの連続した呼び出し部分をまとめてカスタムのクエリー用のメソッドとして扱うことができます。

Taskモデル(app/models/task.rb)にscopeを定義します。

app/models/task.rb

class Task < ApplicationRecord
  before_validation :set_nameless_name
  validates :name, presence: true
  validates :name, length: { maximum: 30 }
  validate :validate_name_not_including_comma

  belongs_to :user

  scope :recent, -> { order(created_at: :desc)}


  private
  
  ・・・
end

これでrecentという名前のscopeを追加できました。

以下のようにクエリー用のメソッドの一種として使うことができます。

tasks = Task.recent  // 全権を新しい順に取得
task = Task.recent.first  // 最も新しいタスクのオブジェクトを取得
task = Task.recent.last  // 最も古いタスクのオブジェクトを取得
tasks = current_user.tasks.recent  // ログインユーザーのタスクを新しい順で取得
tasks = Task.where(user_id: [1,2,5]).recent  // IDが1または2または5のユーザーのタスクを新しい順に取得

フィルタの追加

app/controllers/tasks_controller.rbでは複数のアクションで「idパラメータからタスクオブジェクトを検索して@taskに代入する」という意図の以下のコードが使用されています。

@task = current_user.tasks.find(params[:id])

このように同じコードが重複している場合、この処理に対して変更を行う時に重複箇所全てに変更が必要になります。そこでフィルタを利用して処理の重複を避けるようにします。

app/controllers/tasks_controller.rbを以下のように変更します。

app/controllers/tasks_controller.rb

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  def index
    @tasks = current_user.tasks.order(created_at: :desc);
  end

  def show
  end

  def new
    @task = Task.new
  end

  def create
    @task = current_user.tasks.new(task_params)

    if @task.save
      redirect_to tasks_url, notice: "タスク 「#{@task.name}」を登録しました。"
    else
      render :new
    end
  end

  def edit
  end

  def update
    task.update!(task_params)
    redirect_to tasks_url, notice: "タスク「#{task.name}」を更新しました。"
  end

  def destroy
    task.destroy
    redirect_to tasks_url, notice: "タスク「#{task.name}」を削除しました。"
  end


  private

  def task_params
    params.require(:task).permit(:name, :description)
  end

  def set_task
    @task = current_user.tasks.find(params[:id])
  end
end

まずはset_taskメソッドをprivateメソッドとして定義して共通化したい処理を記述します。

次にbefore_actionメソッドを利用することで、set_taskメソッドが各アクションの実行前に呼び出されるようになりました。

最後に各アクションから共通化した処理を削除します。

このように共通処理をフィルタとして切り出すことで、アクションをシンプルに記述できます。

URLを自動でリンクとして表示

タスクの詳しい説明にURLを記述したいケースが考えられます。URL部分がリンクになっていてクリックした時遷移できるようにしたいです。

しかし文字列の中からURLに該当する部分を検出して、<a>タグを作成せねばならないため手間がかかります。

rails_autolinkというgemを利用すると、このような処理を自動で行えます。

Gemfileに以下のコードを追加してbundleを実行します。

gem 'rails_autolink'

タスクの詳細画面(app/views/tasks/show.html.slim)の詳しい説明を表示する箇所を以下のように修正します。

h1 タスクの詳細

.nav.justify-content-end
  = link_to '一覧', tasks_path, class: 'nav-link'
table.table.table-hover
  tbody
    tr
      th= Task.human_attribute_name(:id)
      td= @task.id
    tr
      th= Task.human_attribute_name(:name)
      td= @task.name
    tr
      th= Task.human_attribute_name(:description)
      td= auto_link(simple_format(h(@task.description), {}, sanitize: false, wrapper_tag: "div"))
    tr
      th= Task.human_attribute_name(:created_at)
      td= @task.created_at
    tr
      th= Task.human_attribute_name(:updated_at)
      td= @task.updated_at

= link_to '編集', edit_task_path, class: 'btn btn-primary mr-3'
= link_to '削除', @task, method: :delete, data: { confirm: "タスク「#{@task.name}」を削除します。よろしいですか?" }, class: 'btn btn-danger'

これでタスクの詳しい説明にURLを記述した場合、自動でリンク表示されます。

まとめ

以上、タスク管理アプリケーションに複数の機能を追加し、複雑な現実に合わせて様々な点で強化できました。

次回からはRailsの自動テストについて学習していきます。

コメント

タイトルとURLをコピーしました