###### tags: `rails` `モデル` `関連付け` # rails でのモデル間の関連付け (1対多) # 前説 これまで[会員登録](https://hackmd.io/@good1luck21/rJ1hJ9bJO)と[新規投稿](https://hackmd.io/@good1luck21/HyU-55bk_)などをまとめましたが、 **今回は上記2つの機能の掛け合わせのご説明をしたいと思います。** みなさんがよく使われるSNSアプリなどは誰が会員登録 or ログインをしていて、その人がどのような投稿をしたのかを**紐付け**ています。 その**紐付け**によってログインするとみなさんが投稿した内容の一覧などが見れるようになっています。 今回はどのように**紐付け**を行うのかについて解説していきたいと思います。 ## 関連付け前準備 - [ ] UserモデルとPostモデルの作成 - [ ] 会員登録用にBcryptの機能をインストールする - [ ] パスワードの暗号化を行うため、`has_secure_password`をUserモデルに記載する 今までの記事で上記内容に関してご説明させて頂きましたので、準備に関してご対応頂けそうでしょうか? 以下に簡単に流れを記載致しますが、まずは皆さんで一旦頑張ってみてもらえたらなと思います! ## コーディング事前準備解説 ### UserモデルとPostモデルの作成 - Userモデル ```bash= rails g model User name:string email:string password_digest:string ``` - Postモデル ```bash= rails g model Post title:string content:string ``` 一旦上記のようにモデルを作っておきましょう!紐付けを実装する段階でカラムを追加しますが、今は基本的な内容でカラムを定義しておけば大丈夫です! モデルを作ったら`rails db:migrate`を忘れずに実行しましょう! ### 会員登録用にBcryptの機能をインストールする ```rails= . ..省略 ... # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.5' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password gem 'bcrypt', '~> 3.1.7' ``` `bcrypt`がデフォルトではコメントアウトされているので、コメントを取り除いてあげましょう! コメントを取ったら忘れずに、`bundle install` を行います! ### has_secure_passwordをUserモデルに記載する ```rails= class User < ApplicationRecord has_secure_password end ``` 何も気にする必要はありません!文言を上記のようにモデルへ追記するだけです! ------------------ ここまでが事前準備の解説になりますが、出来ましたでしょうか? 出来なかった方は諦めずにもう一度トライしてみてください! ------------------ # 序章。会員登録機能の実装 解説は[**ここ**](https://hackmd.io/@good1luck21/rJ1hJ9bJO)を参照してください。 **会員登録の実装が出来ないことには、紐付け出来ない**ので、まずここの実装が出来ない方は復習を行いましょう! # 本番。投稿との紐付け さて、ここから本番になりますので、解説をしていきたいと思います。 まずこれから作成しようとしているユーザーと投稿の紐付けを図で見てみたいと思います。 ```mermaid classDiagram User --|> Post : association User --|> Post1 : association User --|> Post2 : association User --|> Post3 : association class User User : +id 1 User : +name TakeruSato User : +mail "test@test.com" User : +String password User : +String password_confirmation class Post Post : +id 1 Post : +String Title Post : +String Content Post : +user_id 1 class Post1 Post1 : +id 2 Post1 : +String Title Post1 : +String Content Post1 : +user_id 1 class Post2 Post2 : +id 3 Post2 : +String Title Post2 : +String Content Post2 : +user_id 1 class Post3 Post3 : +id 4 Post3 : +String Title Post3 : +String Content Post3 : +user_id 1 ``` 上記のようにユーザー1(例:Takeru)は1人のユーザーですが、そのユーザーが投稿する内容はたくさんあると思います。 上記のような1人のユーザーと複数の投稿を紐付けるのを**モデルの関連付け**といいます。 モデルの関連付けでは、たくさんある側、今回はPost側ではUserに所属する。User側ではたくさんのPostがあるよとそれぞれモデルに定義して上げる必要があります。 今回は下記の通りとなります。 ## モデルの関連付け & Postテーブルへカラム追加 ### モデルの関連付け - Userモデル ```rails= class User < ApplicationRecord has_secure_password has_many :post end ``` - Postモデル ```rails= class Post < ApplicationRecord belongs_to :user end ``` 上記でモデルの関連付けは完了です! ### Postテーブルへカラム追加 カラム追加に関しては[こちら](https://hackmd.io/@good1luck21/BJoMq_w1_#%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E8%BF%BD%E5%8A%A0)を御覧ください! ```bash= rails g migration AddUserIdToPosts user_id:integer ``` Postテーブルに対してどのユーザーが投稿した内容なのかを紐付けるかを識別するために、user_idカラムを追加します。 こうすることで、user_idでPostテーブルの検索なども出来るようになるため、忘れずにつけるようにしましょう。 マイグレーションファイルを作成したら、`rails db:migrate`を実行しましょう! -------------------- ここまでで関連付けに関しては終了です! 以下では実際にrails consoleを利用してユーザーと投稿を紐付けての投稿やユーザーの投稿一覧の取得の仕方を見てみましょう! rails consoleで出来た内容はコントローラーで貼り付けても同様のことが出来ますので、実装に苦労している方はまずコンソールで試してみましょう! -------------------- # rails console で投稿紐付け! ```rails= rails c # 新規User作成 irb(main):001:0> user = User.new(name: "takeru", email: "test@test.com", password: "123456", password_confirmation: "123456") => #<User id: nil, name: "takeru", email: "test@test.com", password_digest: "$2a$12$w9L0gvTlgwNyTegKsMQacu/bhsBVWtwcHCfbt0SW2K3...", created_at: nil, updated_at: nil> irb(main):002:0> user.save (0.1ms) begin transaction User Create (0.9ms) INSERT INTO "users" ("name", "email", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "takeru"], ["email", "test@test.com"], ["password_digest", "$2a$12$w9L0gvTlgwNyTegKsMQacu/bhsBVWtwcHCfbt0SW2K3xqZV/ZzemS"], ["created_at", "2021-01-24 07:54:15.596628"], ["updated_at", "2021-01-24 07:54:15.596628"]] (1.4ms) commit transaction => true # Userに紐付いた投稿の作成 irb(main):001:0> user = User.find_by(id: 1) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => #<User id: 1, name: "takeru", email: "test@test.com", password_digest: "$2a$12$w9L0gvTlgwNyTegKsMQacu/bhsBVWtwcHCfbt0SW2K3...", created_at: "2021-01-24 07:54:15", updated_at: "2021-01-24 07:54:15"> irb(main):002:0> user.posts.build(title: "test", content: "this is test post for user 1") => #<Post id: nil, title: "test", content: "this is test post for user 1", user_id: 1, created_at: nil, updated_at: nil> irb(main):003:0> user.save (0.1ms) begin transaction User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] Post Create (0.6ms) INSERT INTO "posts" ("title", "content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "test"], ["content", "this is test post for user 1"], ["user_id", 1], ["created_at", "2021-01-24 07:59:25.656421"], ["updated_at", "2021-01-24 07:59:25.656421"]] (1.1ms) commit transaction => true # Postを見てみるとuser_idにも「1」が入り、ユーザーに紐付いて投稿されている。 irb(main):006:0> Post.all Post Load (0.2ms) SELECT "posts".* FROM "posts" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Post id: 1, title: "test", content: "this is test post for user 1", user_id: 1, created_at: "2021-01-24 07:59:25", updated_at: "2021-01-24 07:59:25">]> irb(main):007:0> # ユーザーの投稿一覧を取得 irb(main):007:0> user = User.find_by(id: 1) User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => #<User id: 1, name: "takeru", email: "test@test.com", password_digest: "$2a$12$w9L0gvTlgwNyTegKsMQacu/bhsBVWtwcHCfbt0SW2K3...", created_at: "2021-01-24 07:54:15", updated_at: "2021-01-24 07:54:15"> irb(main):008:0> user.posts Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Post id: 1, title: "test", content: "this is test post for user 1", user_id: 1, created_at: "2021-01-24 07:59:25", updated_at: "2021-01-24 07:59:25">]> irb(main):009:0> # 何件か投稿を作成して、一覧として取得できるか見てみる。 irb(main):009:0> user = User.find_by(id: 1) User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => #<User id: 1, name: "takeru", email: "test@test.com", password_digest: "$2a$12$w9L0gvTlgwNyTegKsMQacu/bhsBVWtwcHCfbt0SW2K3...", created_at: "2021-01-24 07:54:15", updated_at: "2021-01-24 07:54:15"> irb(main):010:0> 15.times do |n| irb(main):011:1* user.posts.build(title: "test title #{n + 1}", content: "test content #{n + 1}") irb(main):012:1> end => 15 irb(main):013:0> user.save (5.5ms) commit transaction => true irb(main):014:0> user.posts Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Post id: 1, title: "test", content: "this is test post for user 1", user_id: 1, created_at: "2021-01-24 07:59:25", updated_at: "2021-01-24 07:59:25">, #<Post id: 2, title: "test title 1", content: "test content 1", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 3, title: "test title 2", content: "test content 2", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 4, title: "test title 3", content: "test content 3", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 5, title: "test title 4", content: "test content 4", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 6, title: "test title 5", content: "test content 5", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 7, title: "test title 6", content: "test content 6", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 8, title: "test title 7", content: "test content 7", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 9, title: "test title 8", content: "test content 8", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, #<Post id: 10, title: "test title 9", content: "test content 9", user_id: 1, created_at: "2021-01-24 08:15:48", updated_at: "2021-01-24 08:15:48">, ...]> irb(main):015:0> ``` 上記のようにユーザーとポストを紐付けることで、新しく使えるメソッドもあり、そのメソッドを使用することで効率よく紐付けの投稿などを作成することが出来る。 関連付けを行ったことで、今回の場合使えるようになったメソッドの一覧を以下に記載します。 違ったモデル名などで関連付けを行った場合は適宜名前を変更してみましょう。 | メソッド | 用途 | | -------- | -------- | | user.posts | ユーザーの投稿一覧を返す。 | | post.user | 投稿に紐付いたユーザーを返す。 | | user.posts.build(title: "xxx", content: "yyy") | ユーザーに紐付いた新しい投稿を返す | | user.posts.find(1) | ユーザーに紐付いた投稿ID1を返す。 | 関連付けを行うことで、どのユーザーが投稿したのか判別できるようになったのがわかりましたか? 記事を読むだけではご理解を深めるのは難しいかと思いますので、ここまでの内容をご自身でおさらいしてみましょう!