前回は管理者が質問の順序を変更できるようにルーティングを追加しました。
今回はモデルとコントローラーを作成して対象を上下に移動できるようにします。

コントローラー名は先日読んだWritebookのソースコードの影響を受けています。Writebookは複数の対象をドラッグ・アンド・ドロップできるようになっていて、とても勉強になりました。
# config/routes.rb
  resources :questions, only: %i[ index show ] do
    resources :moves, only: :create, module: :questions
  endモデル Question に表示順に使う sort_order を追加します。
浮動小数を使う方法もありますが今回は整数を使います。
bin/rails g migration add_sort_order_to_questions sort_order:integer:uniqsort_order は null値を受け入れないように null: false を追加します。
# db/migrate/..._add_sort_order_to_questions.rb
add_column :questions, :sort_order, :integer, null: falsebin/rails db:migrateモデルにソート順、バリデーション、上下移動のメソッドを追加します。
# app/models/question.rb
class Question < ApplicationRecord
  RESERVED_SORT_ORDER = -1
  scope :positioned, -> { order(:sort_order) }
  validates :sort_order, presence: true, uniqueness: true
  def move_up
    above = Question.find_by(sort_order: sort_order - 1)
    Question.swap_position(above, self)
  end
  def move_down
    below = Question.find_by(sort_order: sort_order + 1)
    Question.swap_position(self, below)
  end
  def self.swap_position(upper, lower)
    return if upper.nil? || lower.nil?
    ActiveRecord::Base.transaction do
      saved_sort_order = upper.sort_order
      upper.update!(sort_order: RESERVED_SORT_ORDER)
      lower.update!(sort_order: saved_sort_order)
      upper.update!(sort_order: saved_sort_order + 1)
    end
  end
endseedデータにsort_orderを追加します。
判別しやすいようにラジオボタンの質問にA、チェックボックスの質問にBを付けています。
User.find_or_create_by(name: "ユーザー1")
User.find_or_create_by(name: "ユーザー2")
qr1 = QuestionRadio.find_or_create_by(content: "A1 好きな映画のジャンルは?", sort_order: 1)
["アクション", "コメディー", "ドラマ", "ホラー"].each do |content|
  Radio.find_or_create_by(content: content, question: qr1)
end
qr2 = QuestionRadio.find_or_create_by(content: "A2 好きな果物は?", sort_order: 2)
["いちご", "もも", "みかん", "りんご", "ぶどう"].each do |content|
  Radio.find_or_create_by(content: content, question: qr2)
end
qc1 = QuestionCheck.find_or_create_by(content: "B1 好きな映画のジャンルは?", sort_order: 3)
["アクション", "コメディー", "ドラマ", "ホラー"].each do |content|
  Check.find_or_create_by(content: content, question: qc1)
end
qc2 = QuestionCheck.find_or_create_by(content: "B2 好きな果物は?", sort_order: 4)
["いちご", "もも", "みかん", "りんご", "ぶどう"].each do |content|
  Check.find_or_create_by(content: content, question: qc2)
endテーブルとseedデータを作り直します。
bin/rails db:resetコントローラーを生成します。
bin/rails g controller "questions/moves" --no-helper引数directionがupなら上へ、downなら下に移動します。
# app/controllers/questions/moves_controller.rb
class Questions::MovesController < ApplicationController
  def create
    question = Question.find(params[:question_id])
    case params[:direction]
    when "up"
      if question.move_up
        redirect_to questions_url
      end
    when "down"
      if question.move_down
        redirect_to questions_url
      end
    end
  end
endボタンはインラインで並ぶようにします。
# app/views/questions/index.html.erb
<% @questions.positioned.each do |question| %>
  <div>
    <%= link_to question.content,
        question.becomes(Question)
    %>
    <%= button_to "Up",
        question_moves_path(question),
        params: { direction: "up" },
        form: { style: "display: inline" }
    %>
    <%= button_to "Down",
        question_moves_path(question),
        params: { direction: "down" },
        form: { style: "display: inline" }
    %>
  </div>
<% end %>以上になります。
▼この記事がいいね!と思ったらブックマークお願いします

