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
enduser_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
...
endhas_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のお約束に従わずに勝手に名づけた部分を太字で記述します。
- UserMeetingモデルには、2つの関連があり:user_ref、:meeting_refとしました。 それぞれに結び付けるクラス名(class_name)と外部キー(foreign_key)を指定します。
- Userモデルにも関連が2つあり、UserMeetingモデルとの関連を:umsとして作りました。 :attended_meetingsというMeetingクラスへの関連を、:umsを経由し、その経由先の:meeting_refによって関連付けるよう指定します。
- 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 で確認してみます。
手順
- meeting1(サンプル会議1)にはuser1とuser2が参加します。
- meeting2(サンプル会議2)にはuser1とuser3が参加します。
- uaer4がmeeting1とmeeting2に参加します。
- 会議に参加したユーザーを確認します。
- ユーザーが参加した会議を確認します。
Rails console に打ち込むコマンドは次のものです。 まずは準備
- user1 = User.create(name: ‘ユーザー1’)
- user2 = User.create(name: ‘ユーザー2’)
- user3 = User.create(name: ‘ユーザー3’)
- user4 = User.create(name: ‘ユーザー4’)
- meeting1 = Meeting.create(name: ‘サンプル会議1’)
- meeting2 = Meeting.create(name: ‘サンプル会議2’)
次に、多対多を作ります。
- meeting1.attended_user « user1
- meeting1.attended_user « user2
- meeting2.attended_user « user1
- meeting2.attended_user « user3
- user4.attended_meetings « meeting1
- user4.attended_meetings « meeting2
そして、確認します。
- m1 = Meeting.where(name: ‘サンプル会議1’).first
- m2 = Meeting.where(name: ‘サンプル会議2’).first
- u1 = User.where(name: ‘ユーザー1’).first
- u2 = User.where(name: ‘ユーザー2’).first
- u3 = User.where(name: ‘ユーザー3’).first
- u4 = User.where(name: ‘ユーザー4’).first
- m1.attended_users
- m2.attended_users
- u1.attended_meetings
- u2.attended_meetings
- u3.attended_meetings
- 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はすばらしい。