適当おじさんの適当ブログ

技術のことやゲーム開発のことやゲームのことなど自由に雑多に書き連ねます

PythonのORM Oratorで多対多のリレーションシップを定義するメモ

はじめに

Orator はPythonのORMです。このOratorで多対多のモデルを定義する際に色々と困ったことが多かったので、パターンごとに整理してみました。本記事に書かれたソースコードは、以下の環境で動作させています。

  • Python: 3.7.4
  • Orator: 0.9.9
  • MySQL: 5.6.43

本記事で触れる多対多のパターン

  1. デフォルト設定のパターン
  2. 中間テーブルのテーブル名を変更するパターン
  3. 中間テーブルの外部キーを変更するパターン
  4. 外部キーの参照先を id 以外にするパターン

1. デフォルト設定のパターン

以下のような users と groups が多対多の関係にあるテーブルを例に見ていきます。users と groups の id が主キーで、groups_users テーブルの user_id と group_id がそれぞれに対応した外部キーです。Oratorで多対多なスキーマを定義する際の最もシンプルな形です。

f:id:subarunari:20191026005226p:plain

Oratorで多対多のモデルを定義する際は、 belongs_to_many デコレーターを使用します。中間テーブルは外部キーだけで構成されているので、クラスを定義する必要はありません。users テーブルと groups テーブルに対応するクラスだけ定義しています。

class User(Model):
    @belongs_to_many
    def groups(self):
        return Group

class Group(Model):
    @belongs_to_many
    def users(self):
        return User

belongs_to_many デコレータには様々なオプションを指定できます。オプションを指定しない場合、以下のルールでテーブル名や外部キーの解決が自動的に行われます。

  1. 中間テーブルの名前は、多対多関係にあるテーブルの名前をアルファベット順に並べたもの
  2. 中間テーブルの外部キーの名前は、{参照先のテーブル名の単数形_主キー}
  3. 外部キーが参照するカラムは、参照先のテーブルの主キー

今回の例に当てはめてみると、以下のようになります。

  1. users と groups の中間テーブルなので、中間テーブルの名前は groups_users
  2. users と groups の主キーは id なので、外部キーの名前はそれぞれ user_id と group_id
  3. users と groups の主キーは id なので、中間テーブルから参照されるのは users.id と groups.id

中間テーブルの名前や主キーを変更すると、多対多の関連が取得できなくなります。変更した項目に応じて、 belongs_to_many にオプションを指定する必要があります。

2. 中間テーブルのテーブル名を変更する場合

まずは、中間テーブルの名前を users_groups から members に変更してみます。

f:id:subarunari:20191026190945p:plain

中間テーブルの名前を変更した場合は、belong_to_many の第一引数にテーブル名を指定します。これで中間テーブルに独自の名前をつけていても、問題なく多対多の関連が取得できます。

class User(Model):
    @belongs_to_many("members")
    def groups(self):
        return Group

class Group(Model):
    @belongs_to_many("members")
    def users(self):
        return User

3. 中間テーブルのカラム名を変更する場合

membersテーブルの外部キーの先頭にそれぞれ member_ を付与してみます。

f:id:subarunari:20191026181536p:plain

この場合は、belongs_to_many の第二、第三引数に中間テーブルの外部キーを指定します。これで中間テーブルのカラム名を変更しても、関連が取得できるようになります。

class User(Model):
    @belongs_to_many("members", "member_user_id", "member_group_id")
    def groups(self):
        return Group

class Group(Model):
    @belongs_to_many("members", "member_user_id", "member_group_id")
    def users(self):
        return User

4. 外部キーの参照先を id 以外にするパターン

最後に、中間テーブルが参照している users と groups の主キーを id 以外に変更してみます。users の id を同じINT型の individual_number に、groups の id をVARCHAR型の name に変更しています。

f:id:subarunari:20191026220114p:plain

この場合は、belongs_to_many のオプションだけでは対応できません。外部キーが参照するカラム名は主キーであり、モデルクラスの主キーのデフォルトの名前は id です。例えば、主キーが id の usersテーブルの場合は、user_id が参照されます。参照先のテーブルの主キーを id 以外にする場合は、__primary_key__ に主キーとなる文字列を指定します。

class User(Model):
    __primary_key__ = "individual_number"
    @belongs_to_many("members", "user_individual_number", "group_name")
    def groups(self):
        return Group

class Group(Model):
    __primary_key__ = "name"
    __incrementing__ = False
    @belongs_to_many("members", "user_individual_number", "group_name")
    def users(self):
        return User

incrementing について

余談ですが、主キーを整数型以外にした場合は、 __incrementing__ = False を指定しておいたほうが良いです。これを指定しない場合、save()メソッドなどでDBにデータを保存した際に返されるオブジェクトの主キーの値が 0 になってしまいます。

>>> g = Group()
>>> g.name = "__incrementing__ is True"
>>> g.save()
True
>>> g.name
0

主キーを整数型以外にした場合は、忘れずに設定しておきましょう。