7
4

More than 5 years have passed since last update.

fields_forを使って関連を削除する方法

Last updated at Posted at 2015-07-29

fields_forを使って、関連先をチェックボックスで紐付けていたのですが、関連を切りたい場合に困ってしまいました。チェックボックスを外して保存してもうまく削除されずゴミのデータが残ってしまったりしたためです。
これを解決するための方法を見つけたので、メモとして残しておきます。

サンプル

では、サンプルのコードを書きます。

Before

Controller

OrdersController
class OrdersController < ApplicationController
  before_action :set_order, only: [:show, :edit, :update, :destroy]

  def new
    @order = Order.new
    Item.all.each do |item|
      @order.order_items.build(item: item)
    end
  end

  def edit
    Item.all.each do |item|
      @order.order_items.build(item: item) unless @order.items.exists?(item)
    end
  end

  private
    def set_order
      @order = Order.find(params[:id])
    end

    def order_params
      params.require(:order).permit(:title, order_items_attributes: [:id, :item_id])
    end
end

Model

Orderモデルは複数のItemを持ちます。

Orderモデル
class Order < ActiveRecord::Base
  has_many :order_items
  has_many :items, through: :order_items

  accepts_nested_attributes_for :items
end

Orderに持たれるItemモデルです。

Itemモデル
class Item < ActiveRecord::Base
  has_many :order_items
end

OrderとItemを取り持つ中間テーブルを設けます。

OrderItemモデル
class OrderItem < ActiveRecord::Base
  belongs_to :order
  belongs_to :item
end

View

viewにはfields_forの部分を追加しました。
check_boxにはitemのidをvalueとして持たせます。
チェックが入らない場合はhiddenに設定された0が渡ってきます。

views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
  <% if @order.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved:</h2>

      <ul>
      <% @order.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <label>アイテム</label><br>
    <%= f.fields_for :order_items do |fo| %>
        <label>
          <%= fo.hidden_field :id %>
          <%= fo.check_box :item_id, {checked: fo.object.persisted?}, fo.object.item_id %>
          <%= fo.object.item.name %>
        </label>
    <% end%>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

この状態でPOSTすると、チェックが入ってない場合はitem_idが0の状態で中間テーブルに登録されてしまいます。行自体は削除されません。

After

(追記)mark_for_destructionを使うパターンの場合、リレーション先のバリデーションで、presense: trueになっていた場合、item_idに0が渡った状態だと弾かれてエラーになってしまうことがわかったので、やっぱり_destroyを使うように変更しました。

model

変更はOrderモデルです。accepts_nested_attributes_forにallow_destroyを追加します。

Orderモデル
class Order < ActiveRecord::Base
  has_many :order_items
  has_many :items, through: :order_items

  accepts_nested_attributes_for :items, allow_destroy: true
end

View

Viewでは、check_boxで_destroyをつけます。チェックがついているときの値を0、ついてない場合を1とすることで、チェックがない場合は削除フラグが付くというふうにしておきます。

views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
  <% if @order.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved:</h2>

      <ul>
      <% @order.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <label>アイテム</label><br>
    <%= f.fields_for :order_items do |fo| %>
        <label>
          <%= fo.hidden_field :id %>
          <%= fo.hidden_field :item_id %>
          <%= fo.check_box :_destroy, {checked: fo.object.persisted?}, 0, 1 %>
          <%= fo.object.item.name %>
        </label>
    <% end%>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

こうすることで、関連は綺麗に削除されました。

参考URL

7
4
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
7
4