ネストの深い項目を更新するためのモデルの書き方

中間テーブルで扱うデータが`polymorphic`になっていて、それを該当モデルから`through`を使って呼び出すまでは簡単だったんだけれど、それをView側で更新するのが大変だったのでメモとして書いておく。

# has_manyでpolymorphicな関連を取得

まずはpolymorphicなモデルを扱う中間テーブルのモデル。

```rb:Combinationモデル
class Combination < ApplicationRecord
  belongs_to :group, polymorphic: true
  belongs_to :group_item, polymorphic: true

  validates :group_item, presence: true
  validates :group, presence: true
  validates_uniqueness_of :group_id, scope: [:group_type, :group_item_id, :group_item_type]  end
end
```

次に、Memoモデル。
メモを確認できるメンバーを、中間テーブル`combinations`を通じて取得する。
has_manyを2つ定義している。

```rb:Memoモデル
class Memo < ApplicationRecord

  has_many :combinations, as: :group, dependent: :destroy, inverse_of: :group
  has_many :members,
           through: :combinations,
           source: :group_item,
           source_type: 'User',
           autosave: false

  accepts_nested_attributes_for :members
end
```

本来`group_item`はpolymorphicなので、どんなモデルでも入り得るのだが、source_typeを`User`にすることで、`User`のインスタンスとして取得できるようにしてある。


次にUserモデル。一応載せていますが、今回の主役はMemoモデルです。

```rb:Userモデル
class User < ApplicationRecord
  has_many :combinations, as: :group_item, dependent: :destroy
  has_many :memos, inverse_of: :user, dependent: :destroy
end
```

こうしておくと、メモが見られるメンバーをすぐ取得できます（メモが見られる、見られないの実装は省略）

```rb
memo = Memo.first
memo.members # メモが見られるメンバーを取得
```

# View側の実装

View側では、`fields_for`を使って実装していきます。
肝になるのは、`include_id: false, multiple: true`の部分と、`fm.check_box`の`include_hidden: false`です。

```slim
<%= simple_form_for @memo do |f| %>
  <%= f.input :title %>
  <%= f.input :content %>
  <%= f.fields_for :members, User.all, include_id: false, multiple: true do |fm| %>
    <label>
      <%= fm.check_box :id,
                       {
                         include_hidden: false,
                         checked: @memo.member_ids.include?(fm.object.id)
                       },
                       fm.object.id %>
      <%= fm.object.name %>
    </label>
  <% end %>
  <%= f.button :submit %>
<% end %>
```

## include_id: false

fields_forで`include_id: false`を指定しない場合、デフォルトでidが`hidden`フィールドが自動的に追加されてしまい、必ず送られてしまいます。その結果、チェックボックスに関係なく、全員がメモを見られるメンバーになってしまいます。ですので、自動的にidのhiddenフィールドが作られないようにします。

## include_hidden: false

check_boxは、デフォルトでチェックが入っている時の値と、入っていないときの値を送ることができます。
入ってない場合の値は`hidden`フィールドに設定されます。  
今回のパターンではメンバーになるUserのidを送りたいのですが、チェックが入ってない場合は何も送る必要がないので、hiddenフィールド自体を出さないようにします。

# Controllerの実装

controller側では、strong parametersの実装で、membersを値が渡ってくることを許可します。

## 追記（2019-02-12）

更新の際の動作がうまく動いていませんでした。controllerに修正が必要でした。
具体的には、members_attributesに該当するチェックボックスを全て外した状態で更新しようとすると、members_attributesが送られてこないため、あとで説明する`members_attributes=`メソッドが呼ばれず`@memo.members`が更新されませんでした。
members_attributesが送られていない場合は`@memo.members.clear`を呼ぶことで解決しました。

```rb
class MemosController < ApplicationController

  def create
    Memo.transaction do
      @memo = Memo.new(memo_params)
      @memo.save!
    end
  rescue => e
    render :new
  end

  def update
    Memo.transaction do
      @memo.members.clear if memo_params[:members_attributes].blank?
      @memo.update!(memo_params)
    end
  rescue => e
    render :edit
  end


  private
  def memo_params
    params.require(:memo).permit(
      :title,
      :content,
      { members_attributes: [:id] }
    )
  end
end
```

これで終わったかに見えるのですが、実は終わってません…。
@memoを保存する前に、memoにmembersのデータが保存されていることを確認しにいってしまい、エラーが発生します。
（@memoはまだ保存されていないので、membersがあるはずもなく、落ちる）

# Memoモデルのmembers_attributes=の上書き

これは、members_attributesのデータを元に`memo.members`にデータを反映する過程で発生します。そのため、`members_attributes=`メソッドを上書きします。（`members_attributes=`メソッドはhas_manyの設定により、ActiveRecordにより、動的に作られるメソッドです。）

```rb:Memoモデル
class Memo < ApplicationRecord
  # (略)

  def members_attributes=(attributes)
    self.members = User.where(id: attributes.values.pluck('id')
    super
  end

  # (略)
end
```

これにより、中間テーブルの存在を薄くしてシンプルにMemoとUserのやりとりができるようになりました！

# まとめ

polymorphicなデータを扱うときは、リレーションの設定が複雑になるのでハマりやすいですが、設定さえすれば割と綺麗に実装できました。