学生が作らんとしているアプリ,入門書ではなかなか触れられていない際どいところに触れてきよったので,補講をした.以下はその記録から要点を抜粋したものである.
やりたいこと
学生たちは自分たちが授業評価をするアプリを作りたいそうだ.みんなのキャンパス学内版,みたいなものか.それでデータ構造を考えている.一人の学生は複数の授業を履修する.ひとつの授業は複数の学生が履修する.したがって,学生テーブルと授業テーブルを作ると,それは多対多の関係ということになる.
ER図はこんな感じ.
モデルの作成
さて,では必要なモデルを作っていこう.まず,学生モデル(Student)を次の操作で作る.
$ bin/rails g model student name:string sid:string
Running via Spring preloader in process 8711
invoke active_record
create db/migrate/20210625063116_create_students.rb
create app/models/student.rb
invoke test_unit
create test/models/student_test.rb
create test/fixtures/students.yml
$ bin/rails db:migrate
== 20210625063116 CreateStudents: migrating ===================================
-- create_table(:students)
-> 0.0083s
== 20210625063116 CreateStudents: migrated (0.0085s) ==========================
$
次に,授業モデル(Lesson)も次の操作で作る.
$ bin/rails g model lesson name:string teacher:string room:string
Running via Spring preloader in process 8788
invoke active_record
create db/migrate/20210625063223_create_lessons.rb
create app/models/lesson.rb
invoke test_unit
create test/models/lesson_test.rb
create test/fixtures/lessons.yml
$ bin/rails db:migrate
== 20210625063223 CreateLessons: migrating ====================================
-- create_table(:lessons)
-> 0.0052s
== 20210625063223 CreateLessons: migrated (0.0053s) ===========================
$
多対多の関係なので,間を取り持つ学生-授業モデル(StudentLesson)を作る.
これがなぜ必要になるのか,なかなか分かりにくいのだが,順繰りに解き明かしていくことにする.とりあえずは,次の操作で学生テーブルと授業テーブルを参照するモデルである StudentLesson を作る.
$ bin/rails g model student_lesson student:references lesson:references
Running via Spring preloader in process 8906
invoke active_record
create db/migrate/20210625063559_create_student_lessons.rb
create app/models/student_lesson.rb
invoke test_unit
create test/models/student_lesson_test.rb
create test/fixtures/student_lessons.yml
$ bin/rails db:migrate
== 20210625063559 CreateStudentLessons: migrating =============================
-- create_table(:student_lessons)
-> 0.0133s
== 20210625063559 CreateStudentLessons: migrated (0.0133s) ====================
$
モデルクラスの修正
app/models/student_lesson.rb を見てみると,次のようになっている.
class StudentLesson < ApplicationRecord
belongs_to :student
belongs_to :lesson
end
学生と授業の間を取り持つクラスはそれぞれに belongs_to しているので,これはそのままこのとおりにしておけばよい.
app/models/student.rb はどうか.ここには,student_lessons を介してlessonsを持つよということを追記する.具体的には次に示す has_many 句の2行を追加する.
class Student < ApplicationRecord
has_many :student_lessons
has_many :lessons, through: :student_lessons
end
app/models/lesson.rb にも同様の修正を加える.
class Lesson < ApplicationRecord
has_many :student_lessons
has_many :students, through: :student_lessons
end
多対多関係の記述,その効果
このようにしておくと,次の操作が可能になる.ActiveRecord の威力を感じられたい.
まず,Studentをひとつ,作る.これを変数 s に入れる.
$ bin/rails c
Running via Spring preloader in process 9093
Loading development environment (Rails 6.1.4)
irb(main):001:0> s = Student.create(name: 'Jun IIO', sid: '21G0123456J')
TRANSACTION (0.1ms) BEGIN
Student Create (5.7ms) INSERT INTO "students" ("name", "sid", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "Jun IIO"], ["sid", "21G0123456J"], ["created_at", "2021-06-25 06:44:17.699274"], ["updated_at", "2021-06-25 06:44:17.699274"]]
TRANSACTION (6.1ms) COMMIT
=>
#<Student:0x00000001071e0d18
...
次に,Lessonもひとつ,作っておこう.これは変数 l に入れておく.
irb(main):002:0> l = Lesson.create(name: 'Programming I', teacher: 'Taro YAMADA', room: '301')
TRANSACTION (0.1ms) BEGIN
Lesson Create (2.1ms) INSERT INTO "lessons" ("name", "teacher", "room", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "Programming I"], ["teacher", "Taro YAMADA"], ["room", "301"], ["created_at", "2021-06-25 06:44:56.034435"], ["updated_at", "2021-06-25 06:44:56.034435"]]
TRANSACTION (0.3ms) COMMIT
=>
#<Lesson:0x0000000107228050
...
このようにしておくと,s.lessons << l という操作で,s に l を関連付けることができる(sに紐づけられた lessons に l を追加するという操作である).学生 s が授業 l を受講している状態を表していると解釈することができる.この操作で,StudentLesson のエントリが1つ作られていることがおわかりだろうか?(「StudentLesson Create」とログが出ている)
irb(main):003:0> s.lessons << l
TRANSACTION (0.2ms) BEGIN
StudentLesson Create (6.0ms) INSERT INTO "student_lessons" ("student_id", "lesson_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["student_id", 1], ["lesson_id", 1], ["created_at", "2021-06-25 06:45:31.907370"], ["updated_at", "2021-06-25 06:45:31.907370"]]
TRANSACTION (0.3ms) COMMIT
Lesson Load (0.3ms) SELECT "lessons".* FROM "lessons" INNER JOIN "student_lessons" ON "lessons"."id" = "student_lessons"."lesson_id" WHERE "student_lessons"."student_id" = $1 [["student_id", 1]]
=>
[#<Lesson:0x0000000107228050
id: 1,
name: "Programming I",
teacher: "Taro YAMADA",
room: "301",
created_at: Fri, 25 Jun 2021 06:44:56.034435000 UTC +00:00,
updated_at: Fri, 25 Jun 2021 06:44:56.034435000 UTC +00:00>]
わざわざ中間的な関連を示す中間テーブルを用意した威力は,次の操作で確認することができる.すなわち,今の操作「だけ」で授業に参加する学生の一覧も取ることができるようになるということである.
l.students を表示させてみよう.これは,授業 l を受講している学生の一覧を取得する,という操作である.これまで,授業 l に関しては何の操作もしていない.しかし,次のように受講者の一覧(といってもまだ1人だけだが)を自動的に得ることを,確認できるだろう.
irb(main):005:0> l.students
Student Load (0.4ms) SELECT "students".* FROM "students" INNER JOIN "student_lessons" ON "students"."id" = "student_lessons"."student_id" WHERE "student_lessons"."lesson_id" = $1 [["lesson_id", 1]]
=>
[#<Student:0x000000010702c850
id: 1,
name: "Jun IIO",
sid: "21G0123456J",
created_at: Fri, 25 Jun 2021 06:44:17.699274000 UTC +00:00,
updated_at: Fri, 25 Jun 2021 06:44:17.699274000 UTC +00:00>]
irb(main):006:0>
0 件のコメント:
コメントを投稿