はじめに
リソース設計を考えるときに悩んだ結果をまとめました。
コード例はRailsです。
忙しい人のための要約
PUT questions/:id/answer
でupsertする。
DB構造
questionsテーブルとanswerテーブルが1対1の関係です。
answerがquestionに紐づくので、外部キーはanswersに持たせます。
モデル
class Question < ApplicationRecord
has_one :answer, dependent: :destroy
end
class Answer < ApplicationRecord
belongs_to :question
end
特に言うことのないシンプルな形です。
リソースどうしよう?
さて、「ある質問に対する回答を追加する場合」を考えました。
オーソドックスな1対多の場合はこんな感じになるはずです。
POST questions/:id/answers
では、「更新」は?
PATCH questions/:question_id/answers/:id
しかし、これを1対1で想定すると違和感があります。
- 「ある質問に対する回答」は一つしかないのに、
question_id
とanswer_id
をリクエストで送るのが冗長 - 一度回答を作成したら、同じ質問に対して回答を新規作成することはない
「ある質問の回答」を追加することと更新することは同等の意味を持つはずです。
HTTP の
PUT
リクエストメソッドは、新しいリソースを作成するか、指定したリソースの表現をリクエストのペイロードで置き換えます。
PUT - HTTP | MDN
ということなので、Railsのupdateメソッドに相当します。
PUT questions/:id/answer
というリクエストで回答の値を更新することにします。
既にanswerが存在しているときに、新たなanswerをnewとかcreateすること自体がおかしいです。
question1のanswerは一つしかないので、newとedit、createとupdateは等価といえます。
なので、単一リソースのときはedit/updateで作成または更新の行動をしたい。
POST, PATCHを使い分けるためには、リクエスト(URL)がanswerの有無を知る必要があるので、違和感があります。URLがそこまで責任を持つべきではありません。
URLが
「question1のanswerを新しくこの値で作成してくれ」とか「question1のanswerがあるはずだから、この値に書き換えてくれ」
「answerがあるかどうかは知らんけど、question1のanswerの値をこうしてくれ」
ってお願いして、それに答えるのが自然です。
ルーティング
resources :questions do
resource :answer, only: %i[edit update show]
end
コントローラ
#find_or_initialize_by
という便利なメソッドがあるので、それを使うだけで他は普通のCRUDの場合と変わりません。
class AnswersController < ApplicationController
before_action :set_question
def show
@answer = @question.answer
end
def edit
@answer = Answer.find_or_initialize_by(question_id: @question.id)
end
def update
@answer = Answer.find_or_initialize_by(question_id: @question.id)
if @answer.update(answer_params)
redirect_to question_answer_path(@question, @answer)
else
render :edit
end
end
private
def set_question
@question = Question.find(params[:question_id])
end
def answer_params
params.require(:answer).permit(:text)
end
end
終わりに
という考えでこういうリソース設計にしたのですが、補足やオススメの書籍などありましたらコメントをいただけるとありがたいです。