HABTMでforeign_keyとかclass_nameを駆使してみる
最近インターン生に,「まだできないのー?(ニヤニヤ」ってやりながら,Rails3.0のプロジェクトをRails4.1.1まで一気に引き上げるというお仕事をしています.
こんばんは,@h3_potetoです.
ブログもインターン生二人に書いてもらうことが増えると思いますので,暖かく見守ってあげてください.
さて,前の記事で@shunkurosakiくんにRailsの,モデルのAssociationについて書いてもらいました.
Active Record Associationsは,非常に便利なので,使いどころを身につけておくと強くなれます.
そこで,今回はhas_manyやbelongs_toを,ちょっと突っ込んだ使い方してみましょう.
HABTMというのは,CakePHPやRailsでよく使う,モデル間のリレーションです.
その辺は,こんなサイトやら
http://www.stonedot.com/lecture6.html
こんなサイト
http://www38.atwiki.jp/eyes_33/pages/45.html
を参照してくれた方がわかりやすいと思います.
例えば,Userというモデルに,ユーザを登録しておきます.
そのユーザが複数の言語を話せるとしましょう.
言語のモデルをLanguageとして作っておきます.
初期状態では,二つのモデルは以下のような定義になっていると思います.
app/models/user.rb
class User < ActiveRecord::Base end
app/models/language.rb
class Language < ActiveRecord::Base end
DBとしては,usersのテーブルが
id | name |
---|---|
1 | @h3_poteto |
こんな感じ.
languagesのテーブルが
id | language |
---|---|
1 | 日本語 |
2 | 英語 |
となっているとしましょう.
さぁ,これらを関連づけたいのですが,一人のユーザが複数の言語を話せるという状況は往々にしてあります.
ということは,Userは複数のLanguageを参照できなければなりません.
そのために中間テーブルを作ります.
DBの構成としては,
id | user_id | language_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
です.
モデルは,
app/models/user_language.rb
class UserLanguage < ActiveRecord::Base end
みたいなものが生成できていればよろしい.
さて,これをActiveRecordを介した状態で,上手いこと扱えるようにモデルの定義を書きます.
app/models/user.rb
class User < ActiveRecord::Base has_many :user_languages has_many :languages, :through => :user_languages end
app/models/user_language.rb
class UserLanguage < ActiveRecord::Base belongs_to :language end
と,これで,
user = User.find(1) user.languages
みたいな参照の仕方ができるわけですね.
ここまでが,前回@shunkurosakiくんが書いてくれた内容のおさらいです.
ここから,逆側の参照も作ってみる
あれ?日本語が話せるユーザって誰だっけ?と考えたとします.
そうすると,日本語が話せるユーザも複数人いますよね,絶対.
だから,LanguageからUserへも,has_manyしてやります.
app/models/language.rb
class Language < ActiveRecord::Base has_many :user_languages has_many :users, :through => :user_languages end
app/models/user_language.rb
class Language < ActiveRecord::Base belongs_to :language belongs_to :user end
と,書き足してやると,
language = Language.find(1) language.users
というように参照してやれますね.
中間テーブルのカラム名がモデル名と違うケース
そして,それなりに開発していると,実はDBのテーブル構成として,user_idとかlanguage_idっていう,素直にモデル名+idというカラムを作れない状況が出てくるんですね.
そうした場合でも,このモデルの定義を改変していくことで,同じように参照してやることができます.
そのために使うのが,:class_nameと:foreign_key.
例えばですね,DBの中間テーブル,user_languagesを,実はテーブル名も変えて構成も以下のように変えてみました.
people_speakingsテーブル
id | people_id | speaking_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
前述と同じなのですが,カラム名だけが違います.
user_id => people_id language_id => speaking_id
と変更してみました.
これで,先ほどと同様に参照できるように,モデルの定義だけを書き換えて行きます.
注意:user_languagesテーブルをpeople_speakingsテーブルに変更したため,モデル名もuser_lanuage.rbからpeople_speaking.rbに変更になりました.
app/models/user.rb
class User < ActiveRecord::Base has_many :user_languages, :class_name => "PeopleSpeaking", :foreign_key => :people_id has_many :languages, :through => :user_languages end
app/models/language.rb
class Language < ActiveRecord::Base has_many :user_languages, :class_name => "PeopleSpeaking", :foreign_key => :speaking_id has_many :users, :through => :user_languages end
app/models/people_speaking.rb(旧:user_language.rb)
class PeopleSpeaking < ActiveRecord::Base belongs_to :user, :class_name => "User", :foreign_key => :people_id belongs_to :laguage. :class_name => "Language", :foreign_key => :speaking_id end
解説
app/models/user.rbの
has_many :user_languages, :class_name => "PeopleSpeaking", :foreign_key => :people_id
これは,まず中間テーブルをhas_manyさせます.
ただし,詳しく指定をしています.
(class_nameで指定された)PeopleSpeakingモデルの,(foreign_keyで指定された)people_idカラムの値が,自信のモデルのprimary_key,つまりusersテーブルのidと一致するレコードを引っ張ってきて,:user_languagesとしてhas_manyします.
そして,次の行
has_many :users, :through => :user_languages
で,先ほどhas_manyした:user_languagesの,該当するレコードの持つ:userを複数個持てるように指定してあります.
:user_languagesの持つuserとは?
app/models/people_speaking/rb
belongs_to :user, :class_name => "User", :foreign_key => :people_id
ここで,指定されている:userのことです.
この行では,PeopleSpeaking自身が持っている(foreign_keyで指定された):people_idカラムの値が,(class_nameで指定された)Userモデルのprimary_key,つまりusersテーブルのidと一致するusersテーブルのレコードを,PeopleSpeakingモデルの:userとして参照可能にしています.
つまり,テーブル名とhas_manyさせるときに使うカラム名が違う場合,
・foreign_keyは中間テーブルの該当カラム名
・class_nameはどのテーブルのレコードを引っ張りたいのか
・has_manyするときの名前は,belongs_toで指定した名前の複数形
と指定していくと,整合性がとれて行きます.
冒頭で書いたHABTMでは,テーブル名とカラム名をきっちりそろえているので,その辺の指定をRails側で解決できるのですね.
以上,ちょっと難しくモデル間のAssociationについて書いてみました.
あとは,モデルの定義を書きながら,rails consoleで参照してみると良いと思います.
定義が間違っていれば参照できずにエラーが出ると思いますので,そうやって試行錯誤しているとだんだん身に付いてきます.