has_many :throughな多対多のリレーションから、中間テーブルの項目を使って1レコードを取得する
今回はRubyOnRailsでのActive Recordのアソシエーションの話です。
ユーザーが複数の組織に所属可能っていうシステムを作っていく時に、ユーザーのデフォルトの組織を設定したり、
逆に組織に対して代表者を1人決めたいって状況があると思います。
そんな時、中間テーブルのステータスなりフラグを使って1レコード絞って表示したいと思ってやってみると、
案外どーやるんだろ?ってなったりします。
そんな時の自分なりのやり方。
多対多のリレーションとは
多対多の関係とは、お互いのテーブルのレコード同士が複数の相手側レコードと関連付けられる関係の事。
例を出さないと難しいので、最初に例を挙げて、その例に沿って書いていきます。
(例)UserとOrganizationの関係
Userは複数のOrganizationに参加が可能で、Organizationには複数のUserが参加している状況の場合、 1Userから見てOrganizationは多であり、1Organizationから見てUserは多になります。
当然ながら、1対1だと自分のレコードに紐付く相手のIDを記載したら良いけど、
複数に紐付く場合はこのやり方では無理になります。
その分レコードを増やしてしまうと、同じUserが何人もいる事になってしまったりするし、
その分テーブルの項目を作るって事であれば、上限が出来てしまいます。
中間テーブルを作る
この状態を上手い事する為に、中間テーブルを用意します。
中間テーブルにはUserのIDとOrganizationのIDを保持するだけ。
UserとOrganizationの紐付きが増えれば、その分レコードが追加される。
こんな感じ。
これを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を設定していきます。
中間テーブルにフラグを追加して、以下の様な構成にします。
この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の名前が表示されます。