【Rails】fields_for를 사용하여 동일한 폼으로 다른 모델 객체를 편집합니다.

form_with로 투고 폼을 작성할 때, 아이 모델의 컬럼도 같은 폼으로 등록·갱신하기 위해, fields_for를 사용했습니다만, 밸리데이션을 붙이는데 고전했으므로, 기록해 둡니다.

fields_for란?


  • fields_for는 부모 모델과 하위 모델에 대해 하나의 양식 (예 : form_with)에서 편집을 추가하는 데 사용됩니다. Rails Guide 에 의하면 「같은 폼으로 다른 모델 오브젝트도 편집할 수 있도록(듯이) 하고 싶은 경우 등에 편리」라고 하는 것입니다.
  • 이 기사에서는, 부모 모델이 PostRecipe(레시피), 아이 모델이 Ingredient(재료)와 Procedure(순서)의 전제로 진행해 갑니다. 어소시에이션은 기사내에서 기술합니다.

  • 전제:


  • 모델(PostRecipe, Ingredient, Procedure)이나 컨트롤러(post_recipes)의 작성은 끝났다
  • post_recipes 컨트롤러의 새로운 및 생성 작업으로 레시피를 새로 등록합니다.
  • 부모 모델은 PostRecipe (레시피), 하위 모델은 Ingredient (재료) 및 Procedure (절차)이며 각각 일대 다 관계
  • 다음의 복수 모델의 컬럼을, 하나의 폼으로 등록·갱신한다:
  • PostRecipe 모델:title(레시피명), recipe_image(레시피 이미지), introduction(레시피 설명), serving(몇 명전)
  • Ingredient 모델: name(재료명), amount(분량)
  • Procedure 모델:body(만드는 방법)

  • 투고 화면의 이미지(재료·만드는 방법의 부분.전후에)는 이런 느낌입니다↓


  • fields_for 구현



    그럼 구현해 갑니다!

    모델 설명



    👇 accepts_nested_attributes_forallow_destroy: true 를 전달하면 연관된 개체가 삭제됩니다. 아래에 기재되어 있지 않지만, reject_if: all_blank 를 추가하면, 공란의 경우에 데이터 갱신하지 않게 할 수도 있습니다. 옵션에 대해서는 상황에 맞추어 조사해 보면 좋네요.

    app/models/post_recipe.rb
    #一対多のアソシエーション
    has_many :procedures, dependent: :destroy
    has_many :ingredients, dependent: :destroy
    
    #関連付けしたモデルを一緒にデータ保存できるようにする
    accepts_nested_attributes_for :procedures, allow_destroy: true
    accepts_nested_attributes_for :ingredients, allow_destroy: true
    

    app/models/ingredient.rb
    #一対多のアソシエーション
    belongs_to :post_recipe
    

    app/models/procedure.rb
    #一対多のアソシエーション
    belongs_to :post_recipe
    

    컨트롤러 설명



    👇 스트롱 파라미터에 ◯◯_attributes 와 같이 기술해, 아이 모델의 컬럼도 허가하도록(듯이) 합니다. 모델에 allow_destroy: true를 작성하는 경우 _destroy 키를 스트롱 매개 변수에 추가합니다. 이 근처의 거동에 대해서는, Rails Guide 에 기술이 있으므로, 확인해 보세요.

    ※필요한 곳만 꺼내서 쓰고 있습니다

    app/controllers/post_recipes_controller.rb
      def new
        @post_recipe = PostRecipe.new
      end
    
      def create
        @post_recipe = PostRecipe.new(post_recipe_params)
        if @post_recipe.save
          redirect_to post_recipe_path(@post_recipe), notice: "レシピを投稿しました!"
        else
          render :new, alert: "登録できませんでした。お手数ですが、入力内容をご確認のうえ再度お試しください"
        end
      end
    
      private
    
      def post_recipe_params
        params.require(:post_recipe).permit(
          :user_id,
          :title,
          :introduction,
          :recipe_image,
          :serving,
          procedures_attributes: [:body, :_destroy],
          ingredients_attributes: [:name, :amount, :_destroy]
        )
      end
    

    뷰 설명



    app/views/new.html.erb
    
    <div class="container mt-4 align-imtes-center">
      <div class='bg-light mx-auto mb-5'>
    
      <%= form_with model: @post_recipe, local:true do |f| %>
    
     (中略)
    
        <!------------ 材料登録欄 -------------->
          <div class="row">
            <div class="form-inline mb-2">
              <h4 class="mb-0">材料</h4>
              <%= f.text_field :serving, placeholder: "何人分", size:"10x3", class:'ml-4' %>
            </div>
          </div>
    
          <div class="row">
            <table class="table table-borderless mb-0" id="ingredient-table">
              <thead>
                <th class="pb-1"><h6 class="mb-0">材料・調味料</h6></th>
                <th class="pb-1"><h6 class="mb-0">分量</h6></th>
              </thead>
              <tbody>
                <%= f.fields_for :ingredients do |ingredient| %>
                  <tr>
                    <td style="width: 12%" class="py-1"><%= ingredient.text_field :name, placeholder: "にんじん" %></td>
                    <td style="width: 10%" class="py-1"><%= ingredient.text_field :amount, placeholder: "一本" %></td>
                    <td class="align-middle p-0"><input type="button" value="削除" onclick="deleteRow(this)"></td>
                  </tr>
                <% end %>
              </tbody>
            </table>
            <input type="button" value="材料を追加" onclick="addRow('ingredient-table')" class="btn btn-sm btn-color py-0 my-3 ml-2">
          </div>
    
        <!------------ 作成手順登録欄 -------------->
        <div style='max-width: 680px' class="mt-3 px-5 pt-4 mx-auto border-0">
          <div class="row">
            <h4 class="text-center mx-auto mb-0">つくり方</h4>
          </div>
    
          <div class="row">
            <table class="table table-borderless" id="procedure-table">
              <%= f.fields_for :procedures do |procedure| %>
              <tr>
                <td style='width: 70%' class="px-0"><%= procedure.text_area :body, placeholder:"手順を記入", size:'80 x 3', class:'p-2' %> </td>
                <td class="align-middle"><input type="button" value="削除" onclick="deleteRow(this)"></td>
              </tr>
              <% end %>
            </table>
            <input type="button" value="手順を追加" onclick="addRow('procedure-table')" class="btn btn-sm btn-color py-0 mb-3">
          </div>
        </div>
    
        <div class="row mt-4 pb-5">
          <div class="form-inline mx-auto">
            <%= f.submit 'レシピを公開', :name => 'post', style: 'font-size: 20px', class:'mr-5 btn btn-sm btn-warning text-white' %>
            <%= f.submit '下書きに保存', :name => 'update', style: 'font-size: 20px', class:'mr-5 btn btn-sm btn-outline-secondary' %>
          </div>
        </div>
    
        <%= f.hidden_field :user_id, :value => current_user.id %>
        <% end %>
    
      </div>
    </div>
    

    유효성 검사



    여기가 빠진 곳이었습니다만, 여러가지 조사해 시행착오한 결과는 매우 심플했습니다 👇

    모델 설명



    👇 우선 부모 모델 쪽에 이렇게 기술해 줍니다.
    ※필요한 곳만 꺼내서 쓰고 있습니다

    app/post_recipe.rb
      with_options presence: true do
        validates :recipe_image
        validates :serving
        validates :title
        validates :introduction
        validates :ingredients
        validates :procedures
      end
    
      validates :title, length: { maximum: 14 }
      validates :introduction, length: { maximum: 80 }
    

    새 게시시 ingredients 테이블은 두 개의 열(name과 amount)을 fields_for로 업데이트하므로 ingredient.rb 에는 각 열에 대한 검증을 지정해 둡니다 👇

    app/ingredient.rb
      belongs_to :post_recipe
    
      with_options presence: true do
        validates :name
        validates :amount
      end
    

    procedures 테이블은 이번, 갱신 대상이 1 컬럼(body)만이므로, 원래의 기술인 채로 문제 없게 동작합니다👇

    app/models/procedure.rb
    #一対多のアソシエーション
    belongs_to :post_recipe
    

    이상입니다! !

    좋은 웹페이지 즐겨찾기