3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

久々にrailsの並び替え機能を実装したので、その際の作業録です。

実装

昔は、以下のgemを利用した時期もありましたが、jqueryが利用されていることだったり、メンテがされてないとかがあったので、今回は利用しないことにしました
https://github.com/itmammoth/rails_sortable

今回は、stimuls componetsにあるこちらを利用しました。
https://www.stimulus-components.com/docs/stimulus-sortable

javascript関係

yarn add @stimulus-components/sortable sortablejs @rails/request.js
app/javascript/controllers/index.js
import { Application } from '@hotwired/stimulus'
import Sortable from '@stimulus-components/sortable'

const application = Application.start()
application.register('sortable', Sortable)

view 関係

今回テーブル内を入れ替える予定なので、tbody配下全てを対象とするため以下のようにしました。

index.html.haml
      %tbody{ 'data-controller': 'sortable', 'data-sortable-handle-value': '.handle'}
        = render partial: 'test', collection: @tests

並びが終わった直後のポスト先を指定しときます

_test.html.haml
%tr{ 'data-sortable-update-url': position_change_admin_test_path(test)}
  %td.border.cursor-pointer{class: 'handle'}
    %i.fa-solid.fa-up-down
  %td.border
  %#- 省略
routes.rb
    resources :tests, shallow: true do
      patch :position_change, on: :member
    end

ここまで設定していると、とりあえず動く状態にはなるかと思います
l_6814799_124_8b0ce0244265a8ffadc1a11eebd167c2.png

コントローラーにはこんな感じで、パラメータが送られてくるのが確認できました。

 Parameters: {"position"=>"11", "id"=>"1"}

コントローラー 関係

少し力業が否めないですが、自分はこのように実装しました。

考え方:①クエリ上で表示順が欲しい

データが取得した後に番号を割り当てるのもできますが、クエリ内でそれを実装したかったので、以下のようなクエリを生成していました。

controller
ad_num_query = Test.all.select('id , ROW_NUMBER() OVER(ORDER BY tests.display_order ASC) as row_num')
Test.all.joins("JOIN (#{ad_num_query.to_sql}) sub ON tests.id = sub.id")

クエリはこんな感じになります

SELECT
    `tests`.* 
FROM
    `tests` 
    JOIN ( 
        SELECT
            id
            , ROW_NUMBER() OVER (ORDER BY tests.display_order ASC) as row_num 
        FROM
            `tests` 
    ) sub 
        ON tests.id = sub.id 

考え方:②①の順番でレコードを更新したい

既存レコードが1から順次割り当てられてるとは限らないので、割り当てる処理を考えてみました。

admin::testController
ad_num_query = Test.all.select('id , ROW_NUMBER() OVER(ORDER BY tests.display_order ASC) as row_num')
Test.all.joins("JOIN (#{ad_num_query.to_sql}) sub ON tests.id = sub.id")
    .update_all('display_order = sub.row_num')

クエリはこんな感じになります

UPDATE `tests` 
    JOIN ( 
        SELECT
            id
            , ROW_NUMBER() OVER (ORDER BY tests.display_order ASC) as row_num 
        FROM
            `tests` 
    ) sub 
        ON tests.id = sub.id 
SET
    display_order = sub.row_num 

scopeを準備

上記で毎回同じjoinを利用してたので、scope化しました

model
  scope :add_row_num, -> {
    ad_num_query = self.select('id , ROW_NUMBER() OVER(ORDER BY tests.display_order ASC) as row_num')
    joins("JOIN (#{ad_num_query.to_sql}) sub ON tests.id = sub.id")
  }

①と②を踏まえ、コントローラーを作成

コントローラー
def position_change
  @test = Test.find(params[:id])
  return unless @test.update(display_order: params['position'])

  base_query = Test.all
  if params['position'].to_i == 1
    # p '最初'
    base_query.where.not(id: @test).add_row_num
              .update_all('display_order = sub.row_num + 1')
  elsif params['position'].to_i == base_query.size
    # p '最後'
    base_query.where.not(id: @test).add_row_num
              .update_all('display_order = sub.row_num')
  else
    #  p '途中'
    base_query.where.not(id: @test).add_row_num
              .where(display_order: ..params['position'].to_i)
              .update_all('display_order = sub.row_num')

    base_query.where.not(id: @test).add_row_num
              .where(display_order: params['position'].to_i + 1..)
              .update_all('display_order = sub.row_num + 1')
  end
end

※補足説明

【最初(position=1)】
・自分のレコードは先にposition=1で更新
・自分を除くレコード(この時点では、1からスタートされてる)をposition=row_num + 1で更新
【最後(position=last)】
・自分のレコードは先にposition=lastで更新
・自分を除くレコードをposition=row_numで更新
【途中(position=???)】
・自分のレコードは先にposition=lastで更新
・自分を除く自分より数字の小さいレコードをposition=row_numで更新
・自分を除く自分より数字の大きいレコード(この時点では、配置したpositionからスタートされてる)をposition=row_num + 1で更新

さいごに

コントローラーに関しては、色々なやり方があると思います。
自分のやり方も正解とは思っていませんので、あくまで参考レベルで見ていただけると幸いです。
もっといい方法あるよ!という意見がありましたら、ご教授ください。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?