중첩된 창 관련 모델에 데이터를 동시에 저장하는 방법(Rails6.0.0)

결론 친모형 acceptsnested_attributes_for 추가


실증적 환경


・Cloud9 Ubuntu Server
・Rails6.0.0
・Rubby 2.6.3p62(2019-04-16 revision 67580)[x86 64-Linux]

전제 조건


다음 쌍의 다중 관계가 있는 Parent 모델과 Kid 모델을 가정합니다.
※ 1: 혼란을 피하기 위해 "파렌트는 아버지인가요, 어머니인가요?"이런 생각은 없을 것이다.
※ 2: 모델 이름에 불규칙적으로 변하는 명사를 쓰는 것은 번거롭기 때문에 차일드가 아닌 키드를 사용한다.

models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
end
models/kid.rb
class Kid < ApplicationRecord
  belongs_to :parent
end

플러그인 창의 실현


부모 모델의 데이터를 저장할 때 서브 모델의 데이터를 동시에 저장하기 위해accepts_nested_attributes_forParent 모델에 추가합니다.
models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids
end
이렇게 하면 플러그인 창을 사용할 수 있고 한 창에 관련 데이터를 등록할 수 있다.
그리고 모종 Parents 컨트롤러를 사용하여 폼 페이지에서 보내온 params를 받는 빈 실례를 만듭니다.
연관된 서브모델의 빈 인스턴스도 생성합니다.
동시에 서브모델의 Prams를 받아들이기 위해string 매개 변수에서 (위)kids_attributes: [:name, :age, :toy]여기요.
controllers/parents_controller.rb
class ParentsController < ApplicationController

#(中略)

  def new
    @parent = Parent.new
    @parent.kids.build #子モデルの空のインスタンスを作成
  end

  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(中略)

  private

    def parent_params
      #子モデルのパラメーターを受け取れるようにする
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy] 
      )
    end

end
new.html.erb
<div class="container">
  <div class="col-sm-10 col-sm-offset-1">
    <h1 class="text-center">親登録</h1>
    <%= form_with(model: @parent, local: true) do |f| %>
      <div class="field form-group">
        <%= f.label :name %>
        <%= f.text_field :name, class: "form-control" %>
      </div>
      <div class="field form-group">
        <%= f.label :age %>
        <%= f.number_field :age, class: "form-control" %>
      </div>

      <!-- 子モデルのデータを受け取るためのネストされたフォーム -->

      <%= f.fields_for :kids do |kf| %>
      <h1 class="text-center">子登録</h1>
        <div class="field form-group">
          <%= kf.label :name %>
          <%= kf.text_field :name, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :age %>
          <%= kf.number_field :age, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :toy %>
          <%= kf.text_field :toy, class: "form-control" %>
        </div>
      <% end %>

      <div class="field form-group">
        <%= f.submit "上記内容で登録する", class: "btn btn-primary btn-lg btn-block" %>
      </div>
    <% end %>
  </div>
</div>
Bootstrap이 있는 반은 보기 힘들다. 위의f.fields_for는 서브모델 데이터를 수신하는 형식이다.

데이터 업데이트 시 새로운 관련 데이터 저장


여러 개의 빈 실례를 만듭니다. 보기 자체의 창을 추가하지 않고 입력 표시줄을 추가할 수 있습니다.
예를 들어, Parents 디렉터를 변경하면 새로 등록할 때 서브모델 입력 형식 세트를, 업데이트할 때 서브모델 입력 형식 세트를 표시할 수 있습니다.
controllers/parents_controller.rb
class ParentsController < ApplicationController

#(中略)

  def new
    @parent = Parent.new
    @parent.kids.build #子モデルの空のインスタンスを作成
  end

  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(中略)

  def edit
    @parent = Parent.find(params[:id])
    @parent.kids.build #子モデルの空のインスタンスを作成
  end

  def update
    @parent = Parent.find(params[:id])
    if @parent.update(parent_params)
      redirect_to root_url
    else
      render :edit
    end
  end

  def destroy
    @parent = Parent.find(params[:id])
    @parent.destroy
    redirect_to root_url
  end

  private

    def parent_params
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy]
      )
    end

end
편집 동작에서 하위 모델의 빈 실례를 만들면 업데이트할 때 '등록된 하위 모델 데이터' +1개의 입력 창을 만들 수 있습니다.
이렇게 되면 JS 등을 통한 동적 추가보다 입력 형태를 쉽게 늘릴 수 있다.(개수가 제한되어 병목이지만...)
추가 폼을 추가하려면 빈 실례의 수량을 입력하십시오@parent.kids.build부터 시작하다n.times { @parent.kids.build }n개의 입력 창을 생성할 수 있습니다.

입력하지 않았을 때 빈 데이터를 저장하지 않습니다


그러나 추가된 창을 입력하지 않으면 빈 데이터를 저장합니다.
이를 막기 위해 Parent 모델에 추가된accepts_nested_attributes_for 두 번째 매개변수에 Proc를 넘겼다.
models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank?}
end
상기 상황에서 하위 모형이 창의name를 입력하는 것이 비어 있으면 다른 속성을 입력해도 (여기는age,toy) 모두 저장되지 않습니다.
name 또는 age를 입력하지 않은 경우 등록하지 않으려면 다음과 같이 변경됩니다.
models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank? || attributes['age'].blank?}
end
위 내용 외에 서브모델의 검증을 변경하면 저장된 값을 자유롭게 제어할 수 있다.
이외에도 연관된 서브모델의 데이터만 삭제하려는 경우도 있지만, 상세한 경우 Rails 가이드의 "Action View 창 도우미"는 매우 참고할 만하다.

후기


저는 현재 Ruby on Rails에서 공부하는 첫 번째 학자입니다.
나는 내 앱을 만드는 과정에서 며칠 동안 막힌 기능을 발표했다.
잘못된 점이나 건의가 있으면 메시지를 남겨 주세요.

좋은 웹페이지 즐겨찾기