なんでこんな事するの?
formオブジェクトを用いた実装を行いDBに保存した。その時の処理が
class Output
include ActiveModel::Model
attr_accessor :content, :user_id, :book_id, :awareness, :action_plans
def save
awareness = Awareness.new(content: content, book_id: book_id, user_id: user_id)
awareness.save
action_plans.each do |action_plan|
action_plan = ActionPlan.new(time_of_execution: action_plan[:time_of_execution], what_to_do: action_plan[:what_to_do],
how_to_do: action_plan[:how_to_do], awareness_id: awareness.id)
action_plan.save
end
# 別々にレスポンスとして扱うためにハッシュ形式を採用(配列でもいけるが、なんのデータなのかわかりやすくしたい)
output = {}
output[:awareness] = awareness
output[:action_plans] = action_plans
output # 生成したハッシュをコントローラーに返し、レスポンスにする
end
end
こんな感じでAwarenessモデルとActionPlanモデルのデータを保存し、コントローラー側に戻り値として返す、というもの。
これを使ってコントローラー側では
def create
@output = Output.new(output_params)
if @output.valid?
output_save_result = @output.save
# ステータスは手動で設定する。リソース保存時のステータスは201
render status: 201, json: { awareness: output_save_result[:awareness], action_plans: output_save_result[:action_plans] }
else
render status: 422, json: { errors: @output.errors.full_messages } # バリデーションに引っかかった際のステータスは422(Unprocessable entity)
end
end
のようにすることで複数モデルを同時に保存することを可能にしている。
各モデルには1対多のアソシエーションが組まれているのでDBからのデータの参照は
book = Book.find(params[:id])
awarenesses = book.awarenesses
action_plans = awarenesses[0].action_plans
のようにアソシエーションを用いることでが対応可能だが、なんかコードが冗長だしまとまりがない。
そこでDBを参照する機能もformオブジェクトに書いておこう、と思った。
実装内容
class Output
include ActiveModel::Model
attr_accessor :content, :user_id, :book_id, :awareness, :action_plans
def self.fetch_resources(book_id)
book = Book.find(book_id)
outputs = []
output = {}
book.awarenesses.each do |awareness|
output[:awareness] = awareness
output[:action_plans] = awareness.action_plans
outputs << output
end
return outputs
end
end
こんな感じのクラスメソッドを用意した。上記の通りformオブジェクトで保存する各モデルは1対多の関係でアソシエーションが設定されているため
空の配列を用意してそこにどんどん値を放り込んでいくイメージ。最終的に配列を戻り値として返している。引数に渡しているbook_idは
コントローラーでparamsを用いて取得したBookモデルの情報をformオブジェクト側で取得するために使用。
def my_outputs
user_book_relation = UserBook.find_by(user_id: @user.id, book_id: params[:book_id])
if user_book_relation
outputs = Output.fetch_resources(user_book_relation.book.id)
render json: { outputs: outputs }
else
render status: 422, json: { errors: '書籍が推薦図書として追加されていません' }
end
end
コントローラー側ではparamsから飛んでくる値を用いて書籍情報を取得。ここではUserモデルとBookモデルの中間テーブルを
取得し、userとのアソシエーションも確認するようにしている。
個人的に感じた実装のメリット
コントローラーが肥大化するのを抑えることができる。(コントローラにモデルの処理をいくつも書くとわかりにくくなる)
シンプルなデータの取得と条件分岐だけでアクションが完結したので非常に良いのではないでしょうか。