Railsの多対多で関連テーブルの操作色々
色々奥が深いと思った(not http://0xcc.net/misc/bad-knowhow.html 笑)
■前提:記事←関連テーブル→ユーザというモデル関係がある。
ruby>>
has_many :throughのパターン
class Article< ActiveRecord::Base
has_many :articles_users
has_many :users, :through => :articles_users
end
class User < ActiveRecord::Base
has_many :articles_users, :dependent => :destroy
has_many :articles, :through => :articles_users
end
関連テーブルを実テーブルで持つ
class ArticlesUsers < ActiveRecord::Base
belongs_to :article
belongs_to :user
end
<<ruby
■関連テーブルの操作
あるユーザの関連をごそっと別の関連に切り替えたいような場合!
User.articles_users.destroy_allして作り直すという方法もあるが
User.articles_users.replace()という便利なメソッドがある。
AWD第二版(AgileWebDevelopment)で言うとP308。
「○orders.replace(order1,...)
この顧客に関連付けられた注文のセットを、新しいセットに置き換える。現在の子のセットと新しいセットの違いを検出し、それに応じてデータベースの変更を最適化する。」という奴。
ruby>>
/ruby/lib/ruby/gems/1.8/gems/activerecord-2.1.0/lib/active_record/associations/association_collection.rb
# Replace this collection with +other_array+
# This will perform a diff and delete/add only records that have changed.
def replace(other_array)
other_array.each { |val| raise_on_type_mismatch(val) }
load_target
other = other_array.size < 100 ? other_array : other_array.to_set
current = @target.size < 100 ? @target : @target.to_set
@owner.transaction do
delete(@target.select { |v| !other.include?(v) })
concat(other_array.select { |v| !current.include?(v) })
end
end
<<ruby
これは便利。
1.配列を放り込むだけ
「Replace this collection with +other_array+」ということで検索結果、あるいはセッションなどで生成した新しい関連の配列を放り込むだけで使える。
2.以前の関連との差分をチェックして差分だけ反映してくれる。
「This will perform a diff and delete/add only records that have changed.」ということで必要な差分だけチェックして必要なdelete/addをしてくれる。
ちなみにdeleteだと「Railsレシピブック」のP157「Entryオブジェクトobjectとの関連を削除する。Entryオブジェクトobjectの外部キー(blog_id)をNULLにし、関連を削除する。複数の参照元オブジェクトを同時に指定できる。」にある通り、関連テーブルの実レコードは削除されない(外部キーがNULLになるだけ)。関連テーブルにごみが残って気持ち悪い場合は上記モデルのように「:dependent => :destroy」を付けると物理的に削除される。