おーしまブログ

プログラミングやってます

<rails>formオブジェクトでタグ付けの編集、更新、削除を実装する

こんにちは、おーしまです!

今回は、formオブジェクトパターンの編集、更新、削除ができたので紹介します。
createアクションまでは、割と簡単にできましたが、編集と更新が全然分からなかったので、tech-campのメンターに聞いたのですが、最終課題が終わった人は聞けないらしく、門前払いされました。(金払ってるんだから、せめて卒業するまでは教えてくれてもいいんじゃ。。。)でも、ネットで漁りまくって、なんとか自分流で実装することができました。


それでは、早速行きましょう。

コントローラー

  def edit
    @car = Car.find(params[:id])
    @form = SaveCarsTag.new(car: @car)
  end

  def update
    @car = Car.find(params[:id])
    @form = SaveCarsTag.new(car_params, car: @car)
    tag_list = params[:car][:name].split(",")
    if @form.valid?
      @form.save(tag_list)
      return redirect_to car_path(@car)
    else
      render :edit
    end
  end

  def destroy
    @car = Car.find(params[:id])
    if @car.destroy
      redirect_to root_path
    end
  end

モデル

class SaveCarsTag

  include ActiveModel::Model
  attr_accessor :title, :image, :text, :maker_id, :car_name, :body_type_id, :name, :user_id

  with_options presence: true do
    validates :title, length:{maximum: 40}
    validates :text
  end

  #// carがすでに保存されているものか、新規のものかで、PUTとPATCHを分ける
  delegate :persisted?, to: :car

  def initialize(attributes = nil, car: Car.new)
    @car = car
    attributes ||= default_attributes
    super(attributes)
  end

  def save(tag_list)

    ActiveRecord::Base.transaction do
      @car.update(title: title, image: image, text: text, maker_id: maker_id, car_name: car_name, body_type_id: body_type_id, user_id: user_id)

      #// @carに紐付くタグがあれば、car_tagsテーブルの紐付くレコードを全て消去する
      @car.car_tags.each do |tag|
        tag.delete
      end

      #// tag_listのタグの数だけ、tagsテーブルと、car_tagsテーブルに保存する
      tag_list.each do |tag_name|
        tag = Tag.where(name: tag_name).first_or_initialize
        tag.save

        car_tag = CarTag.where(car_id: @car.id, tag_id: tag.id).first_or_initialize
        car_tag.update(car_id: @car.id, tag_id: tag.id)
      end
    end
  end

  def to_model
    car
  end

  private

  attr_reader :car

  def default_attributes
    {
      title: car.title,
      image: car.image,
      text: car.text,
      maker_id: car.maker_id,
      car_name: car.car_name,
      body_type_id: car.body_type_id,
      name: car.tags.pluck(:name).join(',')
    }
  end
  
end

初心者なので、コードは分かりにくかったらすいません。
あと、画像なんですが、編集画面に遷移すると消えてしまうので、毎回設定しないといけなくなっています。ご了承ください。

モデルの命名が下手なので図で説明をすると、こんな感じです。
f:id:tomo_bb_aki0118115:20201030231957p:plain


自分なりに頑張って説明すると、

#コントローラー
def edit
    @car = Car.find(params[:id])
    @form = SaveCarsTag.new(car: @car)
end
#モデル
def initialize(attributes = nil, car: Car.new)
    @car = car
    attributes ||= default_attributes
    super(attributes)
end

formオブジェクトパターンで編集機能を実装する際に、編集画面でそれぞれのデータを表示させておくには、SaveCarsTagのインスタンスを生成して、それをformに持っていかなくてはいけません。なので、モデルにinitializeメソッドを定義して、引数に(car: @car)とすることで、情報がある時は中身を代入しています。そうすることによって、newアクションの時は、中身は空で、editアクションの時は中身がある状態でformに持っていくことができます。super(attributes)で、親クラスのメソッドを使えるようにしているらしくて(?)、ここでは「superがないとinitializeメソッドは使えない」と覚えればいいと思います。

  def update
    @car = Car.find(params[:id])
    @form = SaveCarsTag.new(car_params, car: @car)
    tag_list = params[:car][:name].split(",")
    if @form.valid?
      @form.save(tag_list)
      return redirect_to car_path(@car)
    else
      render :edit
    end
  end

updateアクションでは、initializeメソッドにcar_paramsと@carを渡しています。多分。これで、フォームに入力されたデータが@formの中に入るんだと思います。。tag_listはcreateアクションの時と同じように、タグを配列に変換しています。それをsaveメソッドに運びます。

  def save(tag_list)

    ActiveRecord::Base.transaction do
      @car.update(title: title, image: image, text: text, maker_id: maker_id, car_name: car_name, body_type_id: body_type_id, user_id: user_id)

      #// @carに紐付くタグがあれば、car_tagsテーブルの紐付くレコードを全て消去する
      @car.car_tags.each do |tag|
        tag.delete
      end

      #// tag_listのタグの数だけ、tagsテーブルと、car_tagsテーブルに保存する
      tag_list.each do |tag_name|
        tag = Tag.where(name: tag_name).first_or_initialize
        tag.save

        car_tag = CarTag.where(car_id: @car.id, tag_id: tag.id).first_or_initialize
        car_tag.update(car_id: @car.id, tag_id: tag.id)
      end
    end
  end

ActiveRecord::Base.transaction doはよく分からないけど、書いておいたほうがいいです。。。。。。ちゃんと説明すると、do~endで囲まれた部分で何か処理の失敗が起きた時、do~end間の処理を全てなかったことにするものです。つまり、ここでいうと、carsテーブル、tagsテーブル、car_tagsテーブルの全てにちゃんとデータを保存できたら、おっけい、どれか1つでも失敗すると、全ての保存をなかったことにするということだと思います。ネットからパクったので、意味はわかりますが、なぜ記述しているかは、正直まだわかりません。すみません。

@car.updateでcarsテーブルのデータを更新しています。
次に、@carに紐付くcar_tagsテーブルのデータがあれば、一旦全て消去します。
そして、tag_listにデータが入っていれば、tagsテーブルにすでに存在しているタグか.first_or_initializeで調べて、無いものは保存しています。そうすることによって編集でタグをつけなくすることができるようになります。
その時、中間テーブルのcar_tagsテーブルにCarTag.where(car_id: @car.id, tag_id: tag.id).first_or_initializeで探して、保存しています。


最後

delegate :persisted?, to: :car

これで新規作成か更新かを判別して、formのメソッドをPUTとPATCHで分けているらしいです。

  def to_model
    car
  end

これで、formを飛ばす場所を(#createか#updateか)を判別して、切り替えているらしいです。

attr_reader :car

これは、よくわかりません笑
おそらくこれで、car.titleなど、呼び出しができるようになっているものだと思います。




以上です。

初心者にしては頑張った。タグを更新する時は、最初に全て消していたけど、更新後と更新前で比べて、減ったものだけ削除するほうがよかったかもしれません。それは今後ダメだとわかったら、変えようと思います。
今回は、初めて1日で1つも進まない壁だったので、完成して嬉しかったです。


今回はここまでです。
それでは、また〜

ここはどこ おれはだれ それに近いものがあんだよ 始めようとした奴らも迷い始めてる 怖がらせないでよ そりゃ甘くはないけど まだまだ 夢見ていい世界なんでしょ {UVERwould「ハイ!問題作」}