1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FormオブジェクトでActiveRecordのfind風なメソッドを作ろう

Last updated at Posted at 2021-04-22

なんでこんな事するの?

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とのアソシエーションも確認するようにしている。

個人的に感じた実装のメリット

コントローラーが肥大化するのを抑えることができる。(コントローラにモデルの処理をいくつも書くとわかりにくくなる)
シンプルなデータの取得と条件分岐だけでアクションが完結したので非常に良いのではないでしょうか。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?