Posts 2モデル間の多:多関連
Post
Cancel

2モデル間の多:多関連

Railsのモデル間の関連を具体的に作成します。関連には、2つのモデル間の関連以外に一つのモデルのインスタンス間(テーブルのデータ間)の関連、いわゆる自己関連というものもあります。

ここでは、2つのモデル間に多対多の関連を作りRails consoleで動作を確認します。

1対1関連については2モデル間の1:1関連を参照してください。

1対多関連については2モデル間の1:多関連を参照してください。

自己関連については自己関連(自己結合)を参照してください。

Model

今回、登場するモデルは、 Userモデル、MeetingモデルとUserMeetingモデル の3つです。 テーブルでいえば、usersテーブル、meetingsテーブルとuser_meetingsテーブル です。このuser_meetingsテーブルを仲立ち(「中間テーブル」といいます)としてUserモデル と Meetingモデル の間に多対多関連を作ります。

2モデル間の多対多関連

会議(会合あるいはパーティのようなもの)に参加する参加者を考えます。 一つの会議(会合あるいはパーティ)には複数の人(users)が参加できます。また逆に、一人の人は複数の会議(meetings)に参加することができます。このような関係を多対多の関連というのでした。

したがって、

  • users : meetings は、多対多の関連

となります。

UserモデルとMeetingモデル

Userモデル と Meetingモデル の関連を以下のように作成します。

これら2つのテーブルを結びつけるための中間テーブル(user_meetingsテーブル)を作成します。

$ rails g model user_meeting user:references meeting:references
$ rake db:migrate

もし、usersテーブルとmeetingsテーブルの作成がまだでしたら次のように作ります。

$ rails g model user name:string password:string
$ rails g model meeting name:string #room:references ← 今回にはこのroomは不要です
$ rake db:migrate

次いで、各モデルに以下を追加記述します。

  • user_meeting.rb
 class UserMeeting < ActiveRecord::Base
   belongs_to :user
   belongs_to :meeting
 end

user_meetingsテーブルに参照先のidが入る「user_id」フィールド( user:references で作られました)と「meeting_id」フィールド( meeting:references で作られました)があるので、 belongs_to を記述します。

  • user.rb
class User < ActiveRecord::Base
  has_many :user_meetings
  has_many :meetings, :through => :user_meetings
  ...
end

has_many :user_meetings によって「Userモデル」と「UserMeetingモデル」が1対多の関連である(user_meetingsテーブルに user_id がある)ことを指定します。 has_many :meetings によって「Meetingモデル」への関連を作りますが「meetingsテーブル」に user_id はありません。そこで :through => :user_meetings によって「UserMeetingモデル」を経由して「Meetingモデル」への関連を作ります。

  • meeting.rb Meetingモデルにも同じように記述します。
class Meeting < ActiveRecord::Base
  has_many :user_meetings
  has_many :users, :through => :user_meetings
  ...
end

クラス図ではこのようになります。

:class_name :through :source

先の例ではRailsのお約束に従っているのでいろいろ指定を省略してます。上記と同じ内容を今度は省略しないで記述してみましょう。シンボル名がクラス名やテーブル名と区別できるようにわざと名前を変えて付けます。

  • user_meeting.rb
class UserMeeting < ActiveRecord::Base
  belongs_to :user_ref, :class_name => 'User', :foreign_key => :user_id
  belongs_to :meeting_ref, :class_name => 'Meeting', :foreign_key => :meeting_id
end
  • user.rb
class User < ActiveRecord::Base
  has_many :ums, :class_name => "UserMeeting"
  has_many :attended_meetings, :class_name => "Meeting", :through => :ums, :source => :meeting_ref
  ...
end
  • meeting.rb
class Meeting < ActiveRecord::Base
  has_many :u_m, :class_name => "UserMeeting"
  has_many :attended_users, :class_name => "User", :through => :u_m, :source => :user_ref
  ...
end

ここではRailsのお約束に従わずに勝手に名づけた部分を太字で記述します。

  1. UserMeetingモデルには、2つの関連があり:user_ref:meeting_refとしました。 それぞれに結び付けるクラス名(class_name)と外部キー(foreign_key)を指定します。
  2. Userモデルにも関連が2つあり、UserMeetingモデルとの関連を:umsとして作りました。 :attended_meetingsというMeetingクラスへの関連を、:umsを経由し、その経由先の:meeting_refによって関連付けるよう指定します。
  3. Userモデルと同じですが、Meetingモデルにも関連が2つあり、UserMeetingモデルとの関連を:u_mとして作りました。 :attended_usersというUserクラスへの関連を、:u_mを経由し、その経由先の:user_refによって関連付けるよう指定します。

結果、 ユーザーインスタンス.attended_meetings でそのユーザーが参加した会議の一覧を取得でき、 会議インスタンス.attended_users でその会議に参加したユーザーの一覧が取得できます。

まとめ – :class_name :through :source –

has_many :R, :class_name => "A", :through => "B", :source => "C"

の意味は、『Rが参照されたとき、その参照先のクラスはAで、そこにいたる経路は参照名Bに記述されたクラスの持つ(複数の)参照のうちCという参照名のものである』となります。

各オプションが省略できるときの条件と省略せずに記述したオプションを表の形のまとめておきます。

オプション名省略できる条件省略せずに記述した時の使用例省略した記述
:class_name関連名がモデル名の単数形・複数形である has_one :user, class_name: 'User'
has_many :users, class_name: 'User'
has_one :user
has_many :users
:foreign_key外部キー名が「'モデル名'_id」であるhas_many :users, foreign_key: 'user_id'has_many :users
:throughモデルをJOINする必要がない(UserMeetingモデルをJOINする場合の書き方)
has_many :user_meetings
has_many :users, through: :user_meetings
:source(:through関連を使って)JOINしたモデルの中で
定義されている関連名が約束どおりである
has_many :users, through: :user_meetings, source: :user

class UserMeeting < ActiveRecord::Base
    belongs_to :user
has_many :users, through: :user_meetings

class UserMeeting < ActiveRecord::Base
    belongs_to :user

Rails console で確認

Rails console で確認してみます。

手順

  1. meeting1(サンプル会議1)にはuser1とuser2が参加します。
  2. meeting2(サンプル会議2)にはuser1とuser3が参加します。
  3. uaer4がmeeting1とmeeting2に参加します。
  4. 会議に参加したユーザーを確認します。
  5. ユーザーが参加した会議を確認します。

Rails console に打ち込むコマンドは次のものです。 まずは準備

  1. user1 = User.create(name: ‘ユーザー1’)
  2. user2 = User.create(name: ‘ユーザー2’)
  3. user3 = User.create(name: ‘ユーザー3’)
  4. user4 = User.create(name: ‘ユーザー4’)
  5. meeting1 = Meeting.create(name: ‘サンプル会議1’)
  6. meeting2 = Meeting.create(name: ‘サンプル会議2’)

次に、多対多を作ります。

  1. meeting1.attended_user « user1
  2. meeting1.attended_user « user2
  3. meeting2.attended_user « user1
  4. meeting2.attended_user « user3
  5. user4.attended_meetings « meeting1
  6. user4.attended_meetings « meeting2

そして、確認します。

  1. m1 = Meeting.where(name: ‘サンプル会議1’).first
  2. m2 = Meeting.where(name: ‘サンプル会議2’).first
  3. u1 = User.where(name: ‘ユーザー1’).first
  4. u2 = User.where(name: ‘ユーザー2’).first
  5. u3 = User.where(name: ‘ユーザー3’).first
  6. u4 = User.where(name: ‘ユーザー4’).first
  7. m1.attended_users
  8. m2.attended_users
  9. u1.attended_meetings
  10. u2.attended_meetings
  11. u3.attended_meetings
  12. u4.attended_meetings

やってみましょう。準備段階は省きました。多対多を作るところからです。

meeting1.attended_users << user1

  SQL (0.3ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.482615"], ["meeting_id", 1], ["updated_at", "2015-04-11 07:31:14.482615"], ["user_id", 1]]

  User Load (0.1ms)  SELECT "users".* FROM "users" INNER JOIN "user_meetings" ON "users"."id" = "user_meetings"."user_id" WHERE "user_meetings"."meeting_id" = ?  [["meeting_id", 1]]
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set
meeting1.attended_users << user2

  SQL (0.1ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.496503"], ["meeting_id", 1], ["updated_at", "2015-04-11 07:31:14.496503"], ["user_id", 2]]

+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
| 2  | ユーザー2 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------+----------+-------------------------+-------------------------+
2 rows in set
meeting2.attended_users << user1

  SQL (0.1ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.502368"], ["meeting_id", 2], ["updated_at", "2015-04-11 07:31:14.502368"], ["user_id", 1]]

  User Load (0.1ms)  SELECT "users".* FROM "users" INNER JOIN "user_meetings" ON "users"."id" = "user_meetings"."user_id" WHERE "user_meetings"."meeting_id" = ?  [["meeting_id", 2]]
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set
meeting2.attended_users << user3

  SQL (0.1ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.508609"], ["meeting_id", 2], ["updated_at", "2015-04-11 07:31:14.508609"], ["user_id", 3]]

+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
| 3  | ユーザー3 |          | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------+----------+-------------------------+-------------------------+
2 rows in set
user4.attended_meetings << meeting1

  SQL (0.1ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.516261"], ["meeting_id", 1], ["updated_at", "2015-04-11 07:31:14.516261"], ["user_id", 4]]

  Meeting Load (0.2ms)  SELECT "meetings".* FROM "meetings" INNER JOIN "user_meetings" ON "meetings"."id" = "user_meetings"."meeting_id" WHERE "user_meetings"."user_id" = ?  [["user_id", 4]]
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------------+---------+-------------------------+-------------------------+
1 row in set
user4.attended_meetings << meeting2

  SQL (0.1ms)  INSERT INTO "user_meetings" ("created_at", "meeting_id", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2015-04-11 07:31:14.522096"], ["meeting_id", 2], ["updated_at", "2015-04-11 07:31:14.522096"], ["user_id", 4]]

+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
| 2  | サンプル会議2 |         | 2015-04-11 07:31:14 UTC | 2015-04-11 07:31:14 UTC |
+----+----------------+---------+-------------------------+-------------------------+
2 rows in set

もう確認しなくてもわかりますが、確認です。

m1 = Meeting.where(name: 'サンプル会議1').first
  Meeting Load (0.1ms)  SELECT  "meetings".* FROM "meetings"  WHERE "meetings"."name" = 'サンプル会議1'  ORDER BY "meetings"."id" ASC LIMIT 1
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
1 row in set

m2 = Meeting.where(name: 'サンプル会議2').first
  Meeting Load (0.0ms)  SELECT  "meetings".* FROM "meetings"  WHERE "meetings"."name" = 'サンプル会議2'  ORDER BY "meetings"."id" ASC LIMIT 1
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 2  | サンプル会議2 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
1 row in set

u1 = User.where(name: 'ユーザー1').first
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."name" = 'ユーザー1'  ORDER BY "users"."id" ASC LIMIT 1
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set

u2 = User.where(name: 'ユーザー2').first
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."name" = 'ユーザー2'  ORDER BY "users"."id" ASC LIMIT 1
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 2  | ユーザー2 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set

u3 = User.where(name: 'ユーザー3').first
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."name" = 'ユーザー3'  ORDER BY "users"."id" ASC LIMIT 1
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 3  | ユーザー3 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set

u4 = User.where(name: 'ユーザー4').first
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."name" = 'ユーザー4'  ORDER BY "users"."id" ASC LIMIT 1
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 4  | ユーザー4 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
1 row in set

m1.attended_users
  User Load (0.1ms)  SELECT "users".* FROM "users" INNER JOIN "user_meetings" ON "users"."id" = "user_meetings"."user_id" WHERE "user_meetings"."meeting_id" = ?  [["meeting_id", 1]]
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 2  | ユーザー2 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 4  | ユーザー4 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
3 rows in set

m2.attended_users
  User Load (0.1ms)  SELECT "users".* FROM "users" INNER JOIN "user_meetings" ON "users"."id" = "user_meetings"."user_id" WHERE "user_meetings"."meeting_id" = ?  [["meeting_id", 2]]
+----+----------+----------+-------------------------+-------------------------+
| id | name     | password | created_at              | updated_at              |
+----+----------+----------+-------------------------+-------------------------+
| 1  | ユーザー1 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 3  | ユーザー3 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 4  | ユーザー4 |          | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------+----------+-------------------------+-------------------------+
3 rows in set

u1.attended_meetings
  Meeting Load (0.1ms)  SELECT "meetings".* FROM "meetings" INNER JOIN "user_meetings" ON "meetings"."id" = "user_meetings"."meeting_id" WHERE "user_meetings"."user_id" = ?  [["user_id", 1]]
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 2  | サンプル会議2 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
2 rows in set

u2.attended_meetings
  Meeting Load (0.1ms)  SELECT "meetings".* FROM "meetings" INNER JOIN "user_meetings" ON "meetings"."id" = "user_meetings"."meeting_id" WHERE "user_meetings"."user_id" = ?  [["user_id", 2]]
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
1 row in set

u3.attended_meetings
  Meeting Load (0.1ms)  SELECT "meetings".* FROM "meetings" INNER JOIN "user_meetings" ON "meetings"."id" = "user_meetings"."meeting_id" WHERE "user_meetings"."user_id" = ?  [["user_id", 3]]
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 2  | サンプル会議2 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
1 row in set

u4.attended_meetings
  Meeting Load (0.1ms)  SELECT "meetings".* FROM "meetings" INNER JOIN "user_meetings" ON "meetings"."id" = "user_meetings"."meeting_id" WHERE "user_meetings"."user_id" = ?  [["user_id", 4]]
+----+----------------+---------+-------------------------+-------------------------+
| id | name           | room_id | created_at              | updated_at              |
+----+----------------+---------+-------------------------+-------------------------+
| 1  | サンプル会議1 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
| 2  | サンプル会議2 |         | 2015-04-11 07:52:42 UTC | 2015-04-11 07:52:42 UTC |
+----+----------------+---------+-------------------------+-------------------------+
2 rows in set

期待どおりの結果でした。

なんて簡単!!やっぱりRailsはすばらしい。

シェア
#内容発言者

2モデル間の1:多関連

1. LAMPのインストール

坂井和郎