Ruby on Railsでテーブルを内部(外部)結合するための基礎の覚書き
早速、Ruby on Railsをしていて基礎の基礎であるテーブルの結合でハマった。
ネットでも同じようにハマっていてそれを解決した人や、やはりプロフェッショナルな人が初心者向けに解説しているところはすごく見受けられた。すごい人はすごいなと言う素直な感想だ。
だけどな。俺みたいなアホには、その内容が高度すぎてわからなかったり、求めてる情報となんか違っていたんだよ・・・。
TIPSをメモ帳にまとめておくのも正直だるくなったので基礎の基礎としてこっちに書いておく。
とりあえず、基礎的なところを覚えるにあたって「それなら担当者マスタでしょ!」というなんともまぁ浅知恵な考えで、意気揚々と作り始めた。
で、まずはMySQLに適当に、Oracleでいうところのscott tiger的なテーブルを作った。
そして、こいつのデータを読み出すという実に初歩的なところからスタート。
セッションにユーザーIDを入れてやって、それをキーとしてテーブルのデータを表示するという簡単な画面の作成。
さながら、俺がPHPをまだ始めたばかりのころを思い出して少しうるっと来た。
まぁ、正直なところテーブル単独での読み込みは余裕だった。つーかこんなところでハマるかよ。
だが、問題の発生したのは次の段階。
どうせRailsを使うなら、データベース関連では便利なメソッドいっぱいなので、SQLを直で書くよりも極力その辺の処理はRailsに任せておきたかった。
とりあえず以下のような感じの構成のテーブルを用意した。
「users」テーブル:モデル名は「User」、ファイル名は「user.rb」
プライマリキー(主キー)は「user_id」。そして、ユニークキーと外部キー制御を追加。
「profiles」テーブル:モデル名は「Profile」、ファイル名は「profile.rb」
プライマリキー(主キー)は同じく「user_id」。そして、ユニークキーと外部キー制御も同様。
かなりふざけた内容だが、ちゃんと図画しとかんと自分があとから見ても、あとこの腐れブログを見ている方的にも分からんだろうので・・・ごめんね><
まぁテーブルを見てもらってわかるように、users側のuser_idとprofile側のuser_idを内部結合するような構成。
(後々、他の局面で外部結合とかも出てくるだろうけど、何を差し置いてもまずは内部結合から。)
それを以下のようにしたい。
SQLで書くとこう。至って簡単。
そう、SQLで書けば簡単なのです。それはまた特別な存在だからです。
SELECT USERS.*, PROFILES.* FROM USERS INNER JOIN PROFILES ON USERS.USER_ID = PROFILES.USER_ID;
そして、このテーブル結合を行うためのメソッドをググって調べた。
「joins」とか言うメソッドを使うらしい。そして、ついでに列全体を引っ張ってくるために「select」というメソッドも必要だとかなんとか。
試しにapp/controllersのコントローラー用のファイルに書いてみた。
join_table = User.joins(:profiles).select('users.*, profiles.*, ')
よっしゃこれで結合でk・・・できねぇよ!!
「Can’t join ‘User’ to association named ‘profiles’; perhaps you misspelled it?」とか出てきた。
なんかよく分からんが「結合できません。連結するprofilesをタイプミスしてないでしょうか?」とかそんな感じの意味だろう。
無論、タイプミスなんかしてない。私は常に正しい!私はバウンティーハンターの75KSKだぞ!!
なぜかと思って調べてみた。
Railsのリファレンスを見ると、なんかモデル側に「has_one(many)」「belongs_to」とか言う設定をしなければならないようだ。
結合するテーブルの、親のほうに「has_one(many)」、子の方に「belongs_to」となるらしい。
これもapp/models/当該モデルのファイルに書き加えた。ついでにきちんとしたテーブルの指定とモデルのクラス指定も追加。
user.rbファイル
has_many :profiles, :class_name => :Profile, foreign_key => 'user_id'
profile.rbファイル
belongs_to :users, :class_name => :User, foreign_key => 'user_id'
実際は、クラスの中に書いているが、なぜこのように単独にしてわかりづらくしたかというと・・・この指定間違っているから。
これでうまくいくはず・・・と期待はものの見事に裏切られたんだ。
「undefined local variable or method `foreign_key’ for User(user_id: string, password: string):Class」のエラーが発生。
リファレンスのやつをそのまま改変して入れたのにメソッドまたは変数が見つからないとかどういうことだよ・・・。
いや、まぁアタリはついていたけどね。「foreign_key」の単語の頭にハッシュを表しているはずの「:」が明らかについてないんだもん。
他のサイトさんでも、上記は「foreign_key => ‘KEY’」とか書いてあるからこれで間違いない!絶対に正しいんだ!と自分を疑って試しに「:foreign_key => ‘user_id’」と変更。
user.rbファイル
class User < ApplicationRecord has_many :profiles, :foreign_key => 'user_id' #←試しに変更 end
profile.rbファイル
class Profile < ApplicationRecord belongs_to :users, :foreign_key => 'user_id' #←試しに変更 end
foreign_keyのエラー消えたんですけど。
リファレンスがミスリードを誘うというところに紫の視線と未知数の罠を感じた。
とにかくリファレンスに書かれているサンプルコード「foreign_key => ‘KEY’」はどうも書式として誤っているみたいです。
正確には「:foreign_key => ‘KEY’」です。
リファレンスの引数説明の表記とサンプルコードのところの表記が一致してなかったところを見るに誤植なんだろう。
ちなみによく「foreign_key: ‘KEY’」と書かれているところがあるが、意味合いは「:foreign_key => ‘KEY’」同じ。言わば省略書きらしい。
省略書きをするのは、学習都合上とあとから見たときにとてもよろしくないので、慣れるまでは「:foreign_key => ‘KEY’」みたいな書式で行きたいと思う。
で、話を戻す。怒涛のエラーはまだこれで終わりではなかった。
いや、流石にこれだけちゃんとしたんだからいい加減もう結合ちゃんとできてくれよと願っていたんだ。
非情に無情に無慈悲な「Unknown primary key for table users in model User.」
いい加減にしてや!(^ω^#)ビキビキ
いやいやいや、テーブルにちゃんとプライマリキー設定してるんですけど?
ああ、そうか。わかったよ、兄貴!俺が間違ってた!
きっとテーブルのプライマリキーの設定の仕方が違うんだ・・・
はい、違ってません。
mysqlコマンドで確認したところ、usersテーブルとprofilesテーブルとに「user_id」に対してきちんとプライマリキーを設定していた。
ガラケーを逆折りしたい衝動に駆られた。ワケワカメ状態。
この時点で既にマウスカーソルはRailsの入ったフォルダをドラッグしていた。
そして、そのままダストイn・・・いや、待て待て待てよ・・・?
そういえば、外部キーの制御もテーブルと同時にモデル側に書いていたな?
ということは、同じようにプライマリキーを設定するためのコードがあるのでは?
プライマリキーのコードを調べてみて以下のようにモデルを変更。
user.rbファイル
class User < ApplicationRecord self.primary_key = 'user_id' #←これを追加 has_many :profiles, :foreign_key => 'user_id' end
profile.rbファイル
class Profile < ApplicationRecord self.primary_key = 'user_id' #←これを追加 belongs_to :users, :foreign_key => 'user_id' end
そして、ドヤ顔F5!これで来たらいいな~来なかったら・・・わかるよね??(にこっ)
・・・
・・・・・
・・・・・・・・
きたあああああああああああああああああああああああああああ!!!!!!!!!!!!!!
エラーがようやく全てなくなった。
試しにコントローラーへ内部結合のデータが正しく取れているかどうかを調べるために以下を試しに入力。
join_table = User.joins(:profiles).select('users.*, profiles.*, ').find_by(:user_id => 'yamada') raise join_table.medal.inspect # PHPでいうところのvar_dump()的なもの
このコードの結果、raiseで表示されたのは「鷹」。
内部結合たったひとつでもう感無量。
Railsの書式に則って書くことの歓びを噛み締めた・・・。
で、まぁここまで流れのように長々書いてきたが・・・。
他のサイトさんではやはり管理人さんがその方面に精通しておられるからか、結構飛び飛びで書かれていたり、上記のものならコマンドラインで・・・みたいなサイトもあった。
そういうサイトは知っている人から見たら本当に役に立つ情報なんだけど、俺みたいなRails触り始めとか調べ手には、プログラムコードを単独で知ってもあまり意味はなく、思っているとおりのトラブルシューティングができないんだよね・・・。
というわけで、今回はとりあえず読み手にも自分にもわかりやすく読める(つもりの)ように書いた次第。
というか、最近ブログのキャッチコピーからなんか明後日の方向へと進んでるな~と感じている。
さしづめ「日常の生活とRailsに奮闘するコーダーのブログ」とかでもいいんではないか(皮肉)。