Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:検索条件を引き継いで検索結果のソート順を変更する

検索結果を指定したプロパティで並び変える際に検索条件を引き継いで、ソートする際に少しハマったのでやり方をメモφ(..)

イメージは下記みたいな、検索後のタスクの一覧を期限の降順で並び替えるような機能を想定してます(・∀・)

f:id:madogiwa0124:20180506224144g:plain

検索条件を引き継いでソートする方法

そもそもなぜ検索条件を引き継ぐ必要があるか

何も考えずにソート処理を実装すると下記のような形になるんじゃないかなぁと思うんですが、、、

  • 画面上のソートボタンを押下した際にgetパラメータとして項目とソート(降順・昇順)を設定
  • controllerでパラメーターを取得し、ソート順を設定しタスクの再取得
  • 一覧画面を再描画

上記実装では、少し問題があります。それは、入力された検索条件をcontollerに渡せていないので、ソートボタンを押すと検索条件が未設定の状態で検索処理が行われてしまう恐れがあり下記のように使いにくいものとなってしまいます。。。

f:id:madogiwa0124:20180506225012g:plain

検索条件を引き継いでソートを変更する

先程でも書きましたが、問題はソートを行う際にcontrollerに対して既存の検索条件を設定出来ていないために問題が発生してしまっているので、ソート時にも検索条件をパラメーターとして送信する対応を行いましたφ(..)

_order_form.html.hamlでソートする際にhidden_tagを使って検索条件も合わせてgetパラメーターとして送信するようにしています。

index.html.haml

= render partial: 'search_form', locals: { search_attr: @search_attr }
%table{ border: 1 }
  %thead
    %tr
      %th= Task.human_attribute_name(:title)
      %th= Task.human_attribute_name(:status)
      %th
        = Task.human_attribute_name(:priority)
        = render partial: 'order_form', locals: { target: 'priority', search_attr: @search_attr, order: 'asc' }
        = render partial: 'order_form', locals: { target: 'priority', search_attr: @search_attr, order: 'desc' }

_search_form.html.haml

%div
  = form_tag(search_tasks_path, method: :get) do
    = label_tag :task_title, Task.human_attribute_name(:title)
    = text_field_tag 'task[title]', search_attr[:title]
    = label_tag :task_status, Task.human_attribute_name(:status)
    - statuses = Task.statuses_i18n.invert
    = select_tag 'task[status]', options_for_select(statuses, search_attr[:status]), prompt: '-'
    = submit_tag t('common.search')

_order_form.html.haml

= form_tag(search_tasks_path, method: :get, style: 'display: inline') do
  = hidden_field_tag 'task[title]', search_attr[:title]
  = hidden_field_tag 'task[status]', search_attr[:status]
  = hidden_field_tag "order[#{target}]", order
  = submit_tag order_icon(order) # asc: ▲、desc:▼

ちなみにdeadline DESC等の文字列は、order_stringメソッドを使って生成するようにしてみました(・∀・)

task_controller.rb

class TasksController < ApplicationController
  def index
    prepare_search_attr
    @tasks = Task.all.order(order_string)
  end

  def search
    prepare_search_attr
    @tasks = Task.search(@search_attr).order(order_string)
    render :index
  end

  private

  def order_string
    return 'created_at DESC' unless params.key?(:order)
    order_params.to_h.map { |key, val| "#{key} #{val.upcase}" }.join(',')
  end

  def prepare_search_attr
    @search_attr = { title: '' }
    @search_attr = task_params.delete_if { |_key, val| val.blank? } if params.key?(:task)
  end

  def task_params
    params.require(:task).permit(:title, :description, :status, :priority, :deadline)
  end

  def order_params
    params.require(:order).permit(:deadline, :priority)
  end
end

おわりに

今回はhidden_tagを使って検索条件を引き継いでソートする方法で今回は対応してみました。やりたいことは出来ましたが、もっとRailsっぽい良い方法があるような気がしますね・・・!
なにかまたいい方法があればまとめてみようと思いますφ(..)

参考

hidden_field - リファレンス - - Railsドキュメント