前回は管理者が質問の順序を変更できるようにルーティングを追加しました。
今回はモデルとコントローラーを作成して対象を上下に移動できるようにします。
コントローラー名は先日読んだ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:uniq
sort_order は null値を受け入れないように null: false を追加します。
# db/migrate/..._add_sort_order_to_questions.rb
add_column :questions, :sort_order, :integer, null: false
bin/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
end
seedデータに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 %>
以上になります。
▼この記事がいいね!と思ったらブックマークお願いします