Rails Diary

プログラミングの学習記録です。

ユーザーとタスクの紐付け、タスクとカテゴリの紐付け

経緯

ユーザーとタスクの紐付け方が分からなかったので、コンソールの辺りをやり直してみる

事前準備

① 適当にアプリを作成

$ rails _5.2.6_ new console_app
#(Ruby,Railsのバージョンは任意だったので5系)

② DB作成

$ rails db:create
③ テーブル作成

・usersテーブル name:string age:integer
・tasksテーブル name:string user_id:bigint(references)
・categoriesテーブル name:string
・task_categoriesテーブル task_id:bigint(references)  category_id:bigint(references)

モデル作成

$ rails g model {モデル名(単数)} {カラム名:データ型}
データ型: references(参照)

task: references category: referencesと書くことで、マイグレーションファイルに外部キー制約がつく。また、taskというカラム名を自動的にtask_idにしてくれる。Rails6系であれば、NOT NULL制約も付く。

$ rails g model task_category task_id:integer category_id:integer

$ rails g model task_category task:references category:references

外部キー制約とは

外部キーの概要と制約を使うことのメリット・デメリット - Qiita
主キーと外部キーを使った制約で利用した場合、下記の制限が入る。
1. 存在しない値を外部キーとして登録することはできない。
2. 子テーブルの外部キーに値が登録されている親テーブルのレコードは削除できない。

テーブル作成時にrails g model task_category task:references category:referencesと記述して、外部キーを作成したことでマイグレーションファイルには以下の記述が追加されている。

https://i.gyazo.com/5b76aa173d65dce1eb612cb41c7a95e1.png

foreign_key :trueが外部キー制約
自分は5系でアプリ作成したため、NOT NULL制約は手動で記載。

アソシエーションの設定

app/models/user.rbtask.rbにそれぞれ関係を記述する

ただ、先ほどのreferencesというデータ型のおかげで、task.rbにはあらかじbelongs_to :userの記載がある。

https://i.gyazo.com/7bf4263fb6c0ad202a516785eecc057b.png

(文字的にuserに所属している感が分かりやすいなと・・・)

https://i.gyazo.com/c4974a2cb02bbb5998d94d4331fddf7d.png

解釈が合っているか分からないけど、多分ユーザーはたくさんのタスクを持っているみたいな意味でhas_many :tasksなんだと思う。こちらは手動で記載すること!!

dependent: :destroyは親モデルを削除すると子モデルも一緒に削除される記述。SNSでユーザーが退会したら、そのユーザーの投稿も全て削除されるという例がわかりやすかった。

[Rails] dependent: :destroy について - Qiita

演習部分

演習部分はネタバレ回避のためPCメモに留めておきます。

tasksとcategories、task_categories(中間テーブル)のアソシエーションを記載

データ型: referencesで作成したため、task_category.rbにはbelong_toが記載されている。(タスクとカテゴリに所属している)

task_category.rb

https://i.gyazo.com/242fe7451ea91fae2c06a66060094d1a.png

task.rb

https://i.gyazo.com/03d3231dd2df871b6a0d555b04b27883.png

category.rb

https://i.gyazo.com/00fee5a9b79d1eddc445d8bad6423587.png

through: :task_categoriesとは

タスクとカテゴリは直接紐づいているわけではないので、task_categoriesテーブルを経由してデータを取ってこれる様にthrough: :task_categoriesを記載する。

記載がないと?

category.rbtask.rbにそれぞれhas_many :categories, through: :task_categories has_many :tasks, through: :task_categoriesの記載がない場合、task.categoriesと入力して、直接データを入手することができない。

irb(main):007:0> task = Task.first
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" ORDER BY "tasks"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Task id: 1, name: "Railsの勉強をする", user_id: 1, created_at: "2022-01-08 06:20:29", up...
irb(main):008:0> task.categories
Traceback (most recent call last):
        1: from (irb):8
NoMethodError (undefined method `categories' for #<Task:0x0000000131223e18>)
Did you mean?  task_categories

↑こんな感じでエラーになってしまう。
(上に書いた通り、タスクとカテゴリは直接紐づいていないからtask_categoriesのこと?と聞かれている)

かと言って、下記の様にtask.task_categoriesと打ってもIDしか出てこないので、どの情報が入っているのか分かりづらい。

irb(main):027:0> task.task_categories
  TaskCategory Load (0.1ms)  SELECT  "task_categories".* FROM "task_categories" WHERE "task_categories"."task_id" = ? LIMIT ?  [["task_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<TaskCategory id: 1, task_id: 1, category_id: 1, created_at: "2022-01-08 07:17:51", updated_at: "2022-01-08 07:17:51">]>
このため、through: :task_categoriesを用いる

task.rb

https://i.gyazo.com/03d3231dd2df871b6a0d555b04b27883.png

category.rb

https://i.gyazo.com/00fee5a9b79d1eddc445d8bad6423587.png

task.categoriesと指定しても、データを取ってくることができた!

irb(main):010:0> task = Task.first
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" ORDER BY "tasks"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Task id: 1, name: "Railsの勉強をする", user_id: 1, created_at: "2022-01-08 06:20:29", up...
irb(main):011:0> task.categories
  Category Load (0.1ms)  SELECT  "categories".* FROM "categories" INNER JOIN "task_categories" ON "categories"."id" = "task_categories"."category_id" WHERE "task_categories"."task_id" = ? LIMIT ?  [["task_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Category id: 1, name: "カテゴリ1", created_at: "2022-01-08 06:44:32", updated_at: "2022-01-08 06:44:32">]>

console豆知識

reload!

irb(main):014:0> reload!
Reloading...
=> true