こんにちは、おーしまです!
今回は、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
初心者なので、コードは分かりにくかったらすいません。
あと、画像なんですが、編集画面に遷移すると消えてしまうので、毎回設定しないといけなくなっています。ご了承ください。
モデルの命名が下手なので図で説明をすると、こんな感じです。
自分なりに頑張って説明すると、
#コントローラー 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つも進まない壁だったので、完成して嬉しかったです。
今回はここまでです。
それでは、また〜