has_many :throughな多対多のリレーションから、中間テーブルの項目を使って1レコードを取得する

今回はRubyOnRailsでのActive Recordのアソシエーションの話です。

ユーザーが複数の組織に所属可能っていうシステムを作っていく時に、ユーザーのデフォルトの組織を設定したり、 逆に組織に対して代表者を1人決めたいって状況があると思います。
そんな時、中間テーブルのステータスなりフラグを使って1レコード絞って表示したいと思ってやってみると、 案外どーやるんだろ?ってなったりします。
そんな時の自分なりのやり方。

f:id:daisuke-jp:20160915112130p:plain

多対多のリレーションとは

多対多の関係とは、お互いのテーブルのレコード同士が複数の相手側レコードと関連付けられる関係の事。
例を出さないと難しいので、最初に例を挙げて、その例に沿って書いていきます。

(例)UserとOrganizationの関係

Userは複数のOrganizationに参加が可能で、Organizationには複数のUserが参加している状況の場合、 1Userから見てOrganizationは多であり、1Organizationから見てUserは多になります。

f:id:daisuke-jp:20160915105743p:plain

当然ながら、1対1だと自分のレコードに紐付く相手のIDを記載したら良いけど、 複数に紐付く場合はこのやり方では無理になります。
その分レコードを増やしてしまうと、同じUserが何人もいる事になってしまったりするし、
その分テーブルの項目を作るって事であれば、上限が出来てしまいます。

中間テーブルを作る

この状態を上手い事する為に、中間テーブルを用意します。
中間テーブルにはUserのIDとOrganizationのIDを保持するだけ。
UserとOrganizationの紐付きが増えれば、その分レコードが追加される。
こんな感じ。

f:id:daisuke-jp:20160915110726p:plain

これをRailsのmodelに記述すると、以下の通りとなります。

project/app/models/user.rb

class User < ActiveRecord::Base

  has_many :relations
  has_many :organizations, through: :relations

project/app/models/organization.rb

class Organization < ActiveRecord::Base

  has_many :relations
  has_many :users, through: :relations

project/app/models/relation.rb

class Relation < ActiveRecord::Base

  belongs_to :user
  belongs_to :organization

これでUserとOrganizationの関係は作られました。

user.organizations.first.id

とかは使えます。 もちろん多対多なので、複数形で配列で返ってきます。

UserのデフォルトOrganizationを設定する

ここからUesrのデフォルトで選択されるOrganizationを設定していきます。
中間テーブルにフラグを追加して、以下の様な構成にします。

f:id:daisuke-jp:20160915112130p:plain

このmain_flgがtrueだったら、このOrganizationはこのUserのデフォルトって意味です。
この場合、Controllerやviewで書いてもできるんだけど、以外と綺麗に書けない。
UserとOrganizationの関係性は出来ているんだけど、多対多の為に1レコードを勝手には取ってきてくれない。
できれば、user.current_organization.nameとかして、ユーザーのデフォルトOrganizationを一発で表示したい。
そこで、以下の様にUserModelに書き加えます。

project/app/models/user.rb

class User < ActiveRecord::Base

  has_one :current_user_organization, -> {where(main_flg: true)}, class_name: 'Relation'
  has_one :current_organization, through: :current_user_organization, source: :organization

  has_many :relations
  has_many :organizations, through: :relations

まず中間テーブルでmain_flgがtrueのレコードを取得して、それを使ってOrganizationとのリレーションを作ります。
これでViewで

user.current_organization.name

とかすると、ユーザーのデフォルトOrganizationの名前が表示されます。

このブログの人気の投稿

AnacondaとPythonのインストール(python2とpython3の共存)とDjango

GCPにNutanix CEを自力デプロイの検証をしてみる パート1

PyCharmでAnaconda(Python)とDjangoの開発環境を構築する  その2