(Rails + MySQL を想定しています)
↓のような3TBLがあるとする
class CreateAtbl < ActiveRecord::Migration
def change
create_table :atbls do |t|
t.string :name
end
end
end
class CreateBtbl < ActiveRecord::Migration
def change
create_table :btbls do |t|
t.string :name
t.integer :b_a_id # AテーブルのIDを保持
end
end
end
class CreateCtbl < ActiveRecord::Migration
def change
create_table :ctbls do |t|
t.string :name
t.integer :c_a_id # AテーブルのIDを保持
end
end
end
class Atbl < ActiveRecord::Base
has_many :btbls, foreign_key: "b_a_id"
has_many :ctbls, foreign_key: "c_a_id"
end
class Btbl < ActiveRecord::Base
belongs_to :atbl, foreign_key: "b_a_id"
### ここに Ctbl との has_many を定義したい ###
end
class Ctbl < ActiveRecord::Base
belongs_to :atbl, foreign_key: "c_a_id"
end
# atbls
id name
1 A1
# btbls
id name b_a_id
2 B1 1
# ctbls
id name c_a_id
3 C1 1
Bテーブル => Cテーブルのhas_many
関連 をどう設定するか?
has_many :ctbls, through: :atbl
を使う
- BテーブルとCテーブルはお互いにAテーブルのIDを保持しているので、
:through
でAテーブルを経由すれば紐付く
class Btbl < ActiveRecord::Base
belongs_to :atbl, foreign_key: "b_a_id"
has_many :ctbls, through: :atbl # ★★★
end
[Ruby2.2.2][Rails4.2.1](beers|test_arel)> bdata = Btbl.find(2)
Btbl Load (0.2ms) SELECT `btbls`.* FROM `btbls` WHERE `btbls`.`id` = 2 LIMIT 1
+----+------+--------+
| id | name | b_a_id |
+----+------+--------+
| 2 | B1 | 1 |
+----+------+--------+
1 row in set
[Ruby2.2.2][Rails4.2.1](beers|test_arel)> bdata.ctbls
Ctbl Load (0.3ms) SELECT `ctbls`.* FROM `ctbls` INNER JOIN `atbls` ON `ctbls`.`c_a_id` = `atbls`.`id` WHERE `atbls`.`id` = 1
+----+------+--------+
| id | name | c_a_id |
+----+------+--------+
| 3 | C1 | 1 |
+----+------+--------+
1 row in set
しかし、 お互い同じ内容のカラムを保持しているのにわざわざ経由テーブルと INNER JOIN
するのは無駄
btbls.b_a_id
と ctbls.c_a_id
を直接結合したい
has_many :ctbls, primary_key: "b_a_id", foreign_key: "c_a_id"
を使う
-
has_many :紐付け先, primary_key: "自TBLの結合用カラム", foreign_key: "紐付け先TBLの結合用カラム
と書くと良い
class Btbl < ActiveRecord::Base
belongs_to :atbl, foreign_key: "b_a_id"
has_many :ctbls, primary_key: "b_a_id", foreign_key: "c_a_id" ## ★★★
end
[Ruby2.2.2][Rails4.2.1](beers|test_arel)> bdata = Btbl.find(2)
Btbl Load (0.2ms) SELECT `btbls`.* FROM `btbls` WHERE `btbls`.`id` = 2 LIMIT 1
+----+------+--------+
| id | name | b_a_id |
+----+------+--------+
| 2 | B1 | 1 |
+----+------+--------+
1 row in set
[Ruby2.2.2][Rails4.2.1](beers|test_arel)> bdata.ctbls
Ctbl Load (0.4ms) SELECT `ctbls`.* FROM `ctbls` WHERE `ctbls`.`c_a_id` = 1
+----+------+--------+
| id | name | c_a_id |
+----+------+--------+
| 3 | C1 | 1 |
+----+------+--------+
1 row in set
-
INNER JOIN
してないのでパフォーマンス的に:through
するよりも有利- ただし経由テーブルにデータが存在することがMUST、という場合には当然
:through
すべき
- ただし経由テーブルにデータが存在することがMUST、という場合には当然
:through
の使い方で疑問に思うことがあったので、調査メモとして残しとく