読者です 読者をやめる 読者になる 読者になる

TechBox

スタートアップで働くRails&機械学習エンジニアのブログ。時々GT-Rと旅行。

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の名前が表示されます。

よろしければ、こちらもどうぞ。 www.techbox.jp