<< >>

2006-01-27 優しいRailsの育て方 [長年日記]

[Rails] habtm と has_many :through (ActiveRecord)

habtm は多対多の関係にある2つのモデルを表現するときに非常に便利です。

テーブル (habtm)

groups
id
name
所有
groups_members
group_id
member_id
所有
members
id
name

また、その関係は2つのモデルが各々結合用モデルに対して has_many を持っている構造とも同じです。

テーブル (has_many)

groups
id
name
has_many
assigns
id
group_id
member_id
has_many
members
id
name

このように、2つのモデルが多対多の関係を持つ場合、has_many と habtm はどう違うのでしょうか?DB的には、上記の通りどちらもモデル用の2テーブルと結合を表す1テーブルのみで、違いはありません。(主キーはおいといて)。

has_many と habtm の違い

結局のところ、両者の違いは ActiveRecord が提供してくれるDB操作メソッドだけなのです。

参照(habtm)

Group
members


groups
Member

参照(has_many)

Group
assigns
Assign
assigns
Member

habtm のメリット

habtm は「2つのモデル、かつ、関係性のみ」に特化している分、汎用的な has_many に比べると

  1. 関連するモデルへ直接アクセスするメソッドがある
  2. 関連に対する作成・削除の操作メソッドが充実している

というメリットがあります。しかし、

  • 結合テーブルに追加情報を持たせて色々やりたい
  • 2個以上のモデルを関連付けたい

といった複雑なケースには不向きです。こういう場合には、結合テーブルをモデルとして表に出したhas_many による構成がよいでしょう。

関連先のモデルへのアクセス

しかし、

実行(habtm)

>> berryz = Group.create(:name=>"Berryz工房")
>> maiha  = Member.create(:name=>"石村舞波")

# 関係を作成
>> berryz.members << maiha

# 所属メンバーの一覧
>> berryz.members
=> [#<Member:0xb7a9dc9c @attributes={"name"=>"石村舞波", "id"=>"8"}>]

一度この habtm のエレガントなアクセス方法に慣れてしまうと、

実行(has_many)

>> berryz = Group.create(:name=>"Berryz工房")
>> maiha  = Member.create(:name=>"石村舞波")

# 関係を作成
>> Assign.create(:group=>berryz, :member=>maiha)

# 所属メンバーの一覧
>> berryz.assigns.collect{|assign| assign.member}
=> [#<Member:0xb7a9dc9c @attributes={"name"=>"石村舞波", "id"=>"8"}>]

has_many で実現された多対多での操作は苦痛です。

そこで DHH は考えた

現在の svn trunk では、has_many に :through オプションが導入され、このような中間モデル(Assign)を指定することで、habtm のように直接的に対象となる関連モデルを参照可能になりました。

Model(has_many :through)

class Group < ActiveRecord::Base
  has_many :assigns
  has_many :members, :through=>:assigns
end

class Member < ActiveRecord::Base
  has_many :assigns
  has_many :groups, :through=>:assigns
end

class Assign < ActiveRecord::Base
  belongs_to :group
  belongs_to :member
end         

参照(has_many :throgh)

Group
assigns
↓members
Assign
Member

assigns

実行(has_many :through)

# 所属メンバーの一覧
>> berryz.members
=> [#<Member:0xb7a9dc9c @attributes={"name"=>"石村舞波", "id"=>"8"}>]

現在はまだ参照のみで、追加や削除はまだ habtm の利便性に遠く及びませんが、どんどん機能追加されていくと思います。そして、中間モデル(Assign)は ActiveRecord ですので、好きなように扱うことができます。これは habtm に対する絶対的なメリットでしょう。

今後の予想

ここからは完全に主観と想像ですが、流れというか開発の勢いというか方向性は habtm よりも has_many の方に完全にシフトしている気がします。理由は5つ。

  • habtm も has_many も機能的には似ている
  • habtm は主キーを持たないので AR 上で使うには限界がある (将来的な機能拡張的にも必ず壁にぶつかる)
  • 中間モデルで表現できることによるメリットも多いので has_many の方が将来性もある
  • ticket 的に habtm は結構放置されている (count 不具合、pkey 拡張)
  • DHH 自身が habtm よりも has_many で中間モデルで表現する方に興味を示している (ように見える)

恐らくこれからも has_many :through がさらに機能拡張されていき、Rails 1.2 ぐらいでは、habtm は has_many :through へのマクロになってると予想します。

と、has_many を紹介しておいてなんですが、個人的には habtm のエレガントさにベタ惚れなので、has_many で同等の機能ができる日が来るまで、habtm を愛し続けるつもりです。こっそり「はびたむ」と読んでるくらいラブです。はびたむで気持ちは一杯です。もう habtm MAX Heart です。正直、歌いながら使ってます。(はびったむっ!はびったむっ!はずめにで〜、はびたむ〜、ふ〜たりは〜、はびたむ〜♪)

本日のツッコミ(全2件) [ツッコミを入れる]
_ qluai uqwvintg (2007-05-31 02:00)

vgdj laiht gwkchqjp tyxjleazk tkbx smzfli tifdansj

_ ayucat (2008-03-26 23:03)

ここを見て、has_many :throughにassignsを使うようにしたら、test/functionalsでエラーになってしまいました。<br>インスタンス変数のためのメソッドとしてassignsは定義されてるから、モデル名としては使わないほうがいいみたいでした。<br>Rails 1.xのときは気づかなかったので、Rails 2.0からでしょうか?