LoginSignup
39
37

More than 3 years have passed since last update.

accepts_nested_attributes_forメソッドを使って子テーブルを編集&気に入らないレコードは削除する方法

Last updated at Posted at 2018-05-14

やりたいこと

単語帳アプリにおいて、1科目に登録されてある単語を編集&削除する機能の実装

スクリーンショット 2018-05-14 15.54.55.png

 前提

親テーブル: subject(科目)
子テーブル: word, 主要カラム: "face", "flip"

wordを編集するときにsubjectの編集画面に遷移させ、subjectモデルのインスタンスとwordモデルのインスタンスを同時にいじりたいので、railsのaccepts_nested_attributes_forメソッドの使用が前提

form_forメソッドにより、編集画面において、それぞれのインスタンスにはhiddenタイプのinputタグでインスタンスのidが挿入されており、ハッシュパラメータの中に格納されて送信される。

 豆知識

allow_destroy: trueオプションをモデルのattributesメソッドに追加してやれば、_destroy: 1が付いているパラメータに該当するデータベースのインスタンスは削除される。

View側で適宜、パラメータに_destroy:1を追加してやる方法があるが、今回はreject_ifメソッドをいじってやる方法を紹介したい

 送られるパラメーター構造


"subject"=>{
"title"=>"国語", 
"words_attributes"=>{
 "0"=>{"face"=>"テスト", "flip"=>"してます", "id"=>"54"}, // wordインスタンスの表データ
 "1"=>{"face"=>"", "flip"=>"", "id"=>"55"}, // wordインスタンスの裏面データ。この欄はView側で削除した。 こいつに該当するDBのレコードを消したい!
 "2"=>{"face"=>"明日は", "flip"=>"最高だ"}, //編集画面で新しく追加したインスタンスなのでidがない
 "3"=>{"face"=>"", "flip"=>""}}}, // 新しく作成したデータだが、何もデータが入っていない
"id"=>"35"} //subjectのid

 一番伝えたいこと

今回の最大の難関は、カードを消したときにそれをどうメソッド側で認識して、Delete文をSQLで発行させるかということ。

 答えのコード

Model


class Subject < ApplicationRecord
  belongs_to :user
  has_many :words, dependent: :destroy
  accepts_nested_attributes_for :words, reject_if: :reject_both_blank, allow_destroy: true
  validates :title, presence: true

  def reject_both_blank(attributes)
    if attributes[:id]
      attributes.merge!(_destroy: "1") if attributes[:face].blank? and attributes[:flip].blank?
      !attributes[:face].blank? and attributes[:flip].blank?
    else
      attributes[:face].blank? and attributes[:flip].blank?
    end
  end
end

Controller


before_action :set_subject, only: [:show, :edit, :update]

def update
    @subject.update(create_params)
    redirect_to "/"
end

private
def create_params
  params.require(:subject).permit(:title, words_attributes: [:face, :flip, :id, :_destroy]).merge(user_id: current_user.id)
end

def set_subject
  @subject = Subject.find(params[:id])
end

Model部分の解説

1


accepts_nested_attributes_for :words, reject_if: :reject_both_blank, allow_destroy: true

上のコードで、reject_both_blank関数でTrueが出たパラメータは、送信データから除外させていく。

2


def reject_both_blank(attributes)
    if attributes[:id]

引数のattributesには、送られてきたそれぞれのパラメータがはいる

"0"=>{"face"=>"テスト", "flip"=>"してます", "id"=>"54"}

さっきの例では、上みたいなやつ。
もしもidキーが存在していれば、すなわち、新しく作ったインスタンスではない場合、以下の処理が行われる。
```rb

attributes.merge!(_destroy: "1") if attributes[:face].blank? and attributes[:flip].blank?
!attributes[:face].blank? and attributes[:flip].blank?
```
表と裏が両方空ならば、ユーザーが消したいデータということなので、削除させる仕組みを作る。attributesに入っている1つのパラメーターに_destroyキーとそれに対応する値1が入ったハッシュを追加。

3

allow_destroy: trueオプションが読み込まれ、
paramsに入っていたパラメータ一覧の中から、_destroy: 1のハッシュが入っているパラメータに該当するインスタンスに対してDelete文がSQLで流される。
これで消せます。

 番外


else
      attributes[:face].blank? and attributes[:flip].blank?
    end

ちなみに引数attributesにidキーがなければ、新しく作成したパラメータということで、ユーザは新規にこの単語をwordインスタンスとして保存したいということ。よって、通常のreject_ifオプションに組み込まれる関数のような処理をたどる。
表と裏が両方空ならば、trueが帰るので、reject_ifオプションが反応し、

"3"=>{"face"=>"", "flip"=>""}

上のパラメータはサーバー側で無視され、データベースには保存されない。

39
37
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
39
37