:primary_keyと:foreign_keyを同時指定して、主キーではないけど共通なカラム同士で関連付けする


(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_idctbls.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すべき

:throughの使い方で疑問に思うことがあったので、調査メモとして残しとく