LoginSignup
0
2

More than 3 years have passed since last update.

親子関係のあるモデルのテーブルの値を一度に保存する方法

Last updated at Posted at 2020-02-08

自分用メモ

Gemfile.rb
gem 'nested_form_fields'

gemをインストール。
このgemをインストールするとfields_forが使えるようになる。
fields_forは親以下の子に使うform_forのようなモノ。
fields_forを使うときは親よりもネストさせて書く。

モデル関係

親:post
子:program
子:dealer
孫:program

model/post.rb
  has_many :programs, inverse_of: :post, dependent: :destroy
  has_one :dealer

  accepts_nested_attributes_for :programs, allow_destroy: true
  accepts_nested_attributes_for :dealer, allow_destroy: true

postから見て子供であるprogramとdealerのアソシエーションを書く。

accepts_nested_attributes_forを書くことによって、モデル同士が関連付けられている時に、ネストさせることで一度にまとめて保存できる。
allow_destroy:trueをオプションに書くことによって、fields_forで保存した値を削除できるようになる。(この認識はちょっとあやふやです。)
※注意点
モデルはアソシエーションで単数なのか複数なのかきちんと意識すること。
最初dealerの記述をする時に、accepts_nested_attributes_for :dealers, allow_destroy: true と書いてしまいうまくいかなかった。
モデルのdealerはhas_oneなので「s」はいらない。

model/dealer.rb
  belongs_to :company
  belongs_to :post, optional: true

dealerに紐づく子供はいないのでこれだけ。
今回の話にcompanyは関係ないので割愛。

model/program.rb
  belongs_to :post, optional: true 
  has_many :products, inverse_of: :program, dependent: :destroy

  accepts_nested_attributes_for :products, allow_destroy: true

programから見て親のpostと子供のproductとのアソシエーション。
postと同じようにaccepts_nested_attributes_forを記載。

product.rb
  belongs_to :program, optional: true
  belongs_to :category

productに子供はいないのでこれだけ。
今回の話にcategoryは関係ないので割愛。

コントローラー

posts_controller.rb
def  new
    @post = Post.new
    dealers = @post.build_dealer
    programs = @post.programs.build
    products = programs.products.build
  end

  def  create
    @post = Post.new(post_params)
    @post.save
  end

private

    def post_params
      params.require(:post).permit(:facility_name, :address, :tell, :registration_date, :estimate_sheet, :delivery_date, :delivery_note, :postal_code, :kananame, :user_id, dealer_attributes:[:name, :kananame, :company_id, :post_id, :_destroy, :id], programs_attributes:[:software, :user, :post_id, :_destroy, :id,  products_attributes:[:thing, :category_id, :model_number, :program_id, :_destroy, :id]]).merge(user_id: current_user.id)
    end

大元の親であるposts_controllerのみにnew、createを定義するだけで大丈夫。
ポイントは以下。
・newアクションで子供、孫のbuildを作成する。
 今回で言えば、dealer、program、productのbuild作成。

・paramsのpermitではモデルの記述でaccepts_nested_attributes_forの対象の子は、
 dealer_attributes:という風に モデル名_attributes:と記述する。
・また、子供、孫のハッシュをかっこでネストさせる。
 今回のdealerに子供はいないのでとじかっこの位置には注意。
 programには子供であるproductがいるので、更にネストさせている。
・また、モデルでallow_destroy: trueを記載しているモデルにはpermitの中に:_destroy と :idを記述する。

・createアクションでは、モデルオブジェクトの作成はPost.new(post_params)と大元の親だけで大丈夫。
 変数@postに作成したモデルオブジェクトを入れ、@post.saveとすることで保存される。

 View

new.html.haml
= render 'partial/sidebar'
.main
  %span.main-child
    情報を登録してください
  .main-registration__date
    .main-registration__date-child
      登録日
    = form_for @post, url: posts_path do |f|
      =f.text_field :registration_date, placeholder: "登録日", type: "text", class:"registration__date-input-text"
      .main-facility
        .main-facility-child
          名前
        =f.text_field :facility_name, placeholder: "名前", type: "text", class:"facility-input-text"
      .main-kana
        .main-kana-child
          名前(カナ)
        =f.text_field :kananame, placeholder: "カナ", type: "text", class:"kana-input-text"

      .top
        %span.top-name
          ディーラーを登録してください
        .top-dealer
          .top-dealer-child
          = f.fields_for :dealer do |k|
            .top-dealer-child_box
              .top-dealer-child_box-company_text
                会社名
                .top-dealer-child_box-company
                  = k.collection_select :company_id, Company.all, :id, :name
              .top-dealer-child_box-name_text
                名前
                .top-dealer-child_box-name
                  = k.text_field :name, placeholder: "名前", type: "text", class:"dealer-input-text", id: "dealer_text"
              .top-dealer-child_box-kana_text
                名前(カナ)
                .top-dealer-child_box-kana
                  = k.text_field :kananame, placeholder: "カナ", type: "text", class:"dealer_kana-input-text", id: "dealer_kana_text"

      .best
        %span.best-name
          納品内容を登録してください
        .best-software
          .best-software-child
            ソフトウェア
          = f.fields_for :programs do |d|
            #best-software-child_box
              = d.text_field :software, placeholder: "name", type: "text", class:"program-input-text", id: "program_text"

            .products
              .products-text_box
                = d.fields_for :products do |s|
                  .products-text_box-category_text
                    カテゴリー
                    .products-text_box-category
                      = s.collection_select :category_id, Category.all, :id, :name
                  .products-text_box-product_text
                    ハードウェア
                    .products-text_box-product
                      = s.text_field :thing, placeholder: "products", type: "text", class:"products-input-text", id: "product_text"

            .footer
              = f.submit "送信", class:"products_button"

本当のコードは長すぎるので一部削除。
form_forを使って、まず親であるpostを設定。

form_forの書き方
= form_for モデル, url: createのpath do |f|
モデル部分にモデルオブジェクトをいれる、今回は@post
url部分にはpostのcreateアクションのpathをいれる(コマンドrails routesでわかる)

続いてpostの子供であるdealerを設定。
子供以下は全てfields_forを使う。

fields_forの書き方
= f.fields_for :モデル do |k|
・モデル部分にシンボルを使ってモデル名を記載。
 ここでも単数なのか複数なのか意識すること。
・fields_forの前のfはpostのform_forで使っている変数の|f|←コイツ
 必ず親の変数をつけること。
・hamlの場合必ず親のform_forやfields_forよりも中にネストさせること。
※programのビューの塊がdealerのfields_forよりネストされていない理由はprogramがdealerと同じ位置の子供だから。
もしprogramがdealerの子供であればネストさせないといけない。

fields_forさえ使いこなせれば、大抵の親子関係の値は一度に保存可能なはず。

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