has_many throughなデータの関連付けをするビューの書き方

Ruby on Railsのお話です。has_many で through な多対多のデータを扱うときの関連付けを行う方法。

たとえば、以下のようなActiveRecord

class Book
  has_many :book_genres
  has_many :genres, :through => :book_genres
end

class BookGenre
  belongs_to :book
  belongs_to :genre
end

class Genre
  has_many :book_genres
  has_many :books, :through => :book_genres
end

Bookの編集画面からGenreの関連付けをしたいならば、app/views/books/_form.html.erb に以下のように書いてやります。

<%= form_for(@book) do |f| %>
... 
<% Genres.all.each do |genre| %>
  <%= check_box_tag("book[genre_ids][]", genre.id, @book.genre_ids.include?(genre.id)) %><%= genre.name %>
<% end %>
... 
<% end %>

book[genre_ids] という名前のパラメータに genre の id をセットして送信してやれば @book.update_attributes(param[:book]) で中間テーブルも一緒に更新されるようになるのです。とても簡単ですね。

ところが…

チェックボックスを全て外したときは関連を全部削除してほしいのですが、削除されません。チェックボックスを全部外すと book[genre_ids] というパラメータ自体が送られず、ActiveRecord さんは genre_ids を変更しなくていいんだね?と判断します。

対処法。hiddenでダミーのパラメータを作るだけ。

<%= form_for(@book) do |f| %>
... 
<% Genre.all.each do |genre| %>
  <%= check_box_tag("book[genre_ids][]", genre.id, @book.genre_ids.include?(genre.id)) %><%= genre.name %>
<% end %>
<input type="hidden" name="book[genre_ids][]">
... 
<% end %>

params[:book][:genre_ids]の値に ["1", "2", ""] のような空文字が入りますが、更新時に無視されるの問題ないようです。

こういうコードはviewに書かずにヘルパにまとめた方がいい、というわけでGemにしておきました。10行ぐらいのコードですけど…、テストも書いてないのでrubygems.orgにはアップしてません。bundlerのgit経由で使ってください。

gem "checkboxes_helper", ">=0.1.0", :git => "git://github.com/paulownia/checkboxes_helper.git"

<%= form_for(@book) do |f| %>
... 
<%= f.collection_check_boxes(":genres", Genre.all, :id, :name %><%= genre.name %>
... 
<%= f.check_boxes(":genres", Genre.all.map { |g| [g.id, g.name] } %><%= genre.name %>
...
<% end %>

みたいな感じに使います。