Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け

←[Rails 6で認証認可入り掲示板APIを構築する #11 userモデルのテストとバリデーション追加](https://qiita.com/rf_p/items/59b83f2f799d5a0c537c)

# postからuserへの関連付けをする

postとuserの関連付けを行います。
想定読者がRailsチュートリアル完了済み前提のため意味の説明は割愛しますが、postに`belongs_to :user`を、userに`has_many :posts`を追加しましょう。

```powershell
$ rails g migration AddUserIdToPosts user:references
```

レコードがある状態だとnotnull制約に引っかかってmigrationがエラーになるので、db:resetしてしまいます（乱暴）

```powershell
$ rails db:reset
$ rails db:migrate
```

```diff:app/models/post.rb
...
 class Post < ApplicationRecord
+  belongs_to :user
+
...
```

```diff:app/models/user.rb
...
   include DeviseTokenAuth::Concerns::User

 
+  has_many :posts, dependent: :destroy
+
...
```

2つのテーブルの関連付けを行ったら、ちゃんと動作するか`rails c`で実験してみます。

```powershell
$ rails  c
[1] pry(main)> user = User.create!(name: "hoge", email: "test@example.com",  password: "password")
[2] pry(main)> post = Post.create!(subject: "test", body: "testtest", user: user)
[3] pry(main)> user.posts
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 1]]
=> [#<Post:0x000000000488dbb0
  id: 1,
  subject: "test",
  body: "testtest",
  created_at: Tue, 08 Sep 2020 08:36:20 UTC +00:00,
  updated_at: Tue, 08 Sep 2020 08:36:20 UTC +00:00,
  user_id: 1>]
[4] pry(main)> post.user
=> #<User id: 1, provider: "email", uid: "test@example.com", name: "hoge", email: "test@example.com", created_at: "2020-09-08 08:36:11", updated_at: "2020-09-08 08:36:11">
```

どうやら、無事userからpostsを呼んだり、postからuserを呼んだりできていますね。

# postのserializerを直す

postsのAPIから、ユーザーのIDと名前、メールアドレスを取得したいと思います。
その際に直すべきはserializerとcontroller。
最低限動くにはserializerだけで良いのですが、controllerも手を入れないとN+1問題という無駄なSQLが大量に流れてパフォーマンスを落とす状態になるのでご注意ください。

```diff:app/serializers/post_serializer.rb
...
 class PostSerializer < ActiveModel::Serializer
   attributes :id, :subject, :body
+  belongs_to :user
```

こうするとuserがくっついてきます。

```powershell
$ curl localhost:8080/v1/posts/1
{"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"provider":"email","uid":"test@example.com","name":"hoge","email":"test@example.com","created_at":"2020-09-08T08:36:11.972Z","updated_at":"2020-09-08T08:36:11.972Z"}}}
```

くっついてきたは良いけど、なんかuserの不要な情報までいっぱい取れてきてしまいましたね。
userのserializerがないので追加しましょう。

# userのserializerを作る

modelを作った際にserializerは自動生成されるのですが、devise_token_authでmodel生成したため手動でコマンドを叩きます。
なお、devise_token_authによって自動生成されたcontrollerのレスポンスjsonはactiveModelSerializerが効きません。もし有効化したい場合はdevise系のcontrollerをオーバーライドする必要があるのですが、今回は割愛します。

今後postモデルからuser

```powershell
$ rails g serializer user
```

```app/serializers/user_serializer.rb
# frozen_string_literal: true

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :email
end
```

```powershell
$ curl localhost:8080/v1/posts/1
{"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"name":"hoge","email":"test@example.com"}}}
```

これでひとまずOK。

# N+1問題への対応

それでは、複数ユーザー・複数投稿データをrails cで作ってみて、`curl localhost:8080/v1/posts`を叩いてみます。
無事にデータは取ってこれますが、`rails s`で立ち上げているターミナルに移動してみると…

```powershell
Started GET "/v1/posts" for 127.0.0.1 at 2020-09-08 08:48:08 +0000
Processing by V1::PostsController#index as */*
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT $1  [["LIMIT", 20]]
  ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers]   User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
[active_model_serializers]   ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers]   CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
[active_model_serializers]   ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers]   CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
[active_model_serializers]   ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers]   User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
...
[active_model_serializers]   ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers]   CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
[active_model_serializers]   ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Json (30.42ms)
Completed 200 OK in 34ms (Views: 32.5ms | ActiveRecord: 0.8ms | Allocations: 21448)
```

省略していますが、こんな感じに大量のSQLが流れています。
これがN+1問題です。

postに紐づくuserを、1レコードずつfindして取ってきているので無駄なSQLが大量に流れます。

これは`Post.all`してくるタイミングでincludesしておけばOKです。

```diff:app/controllers/v1/posts_controller.rb
     def index
-      posts = Post.order(created_at: :desc).limit(20)
+      posts = Post.includes(:user).order(created_at: :desc).limit(20)
       render json: posts
     end
```


```powershell
Started GET "/v1/posts" for 127.0.0.1 at 2020-09-08 08:51:50 +0000
Processing by V1::PostsController#index as */*
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT $1  [["LIMIT", 20]]
  ↳ app/controllers/v1/posts_controller.rb:12:in `index'
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2, $3)  [["id", 3], ["id", 2], ["id", 1]]
  ↳ app/controllers/v1/posts_controller.rb:12:in `index'
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Json (5.32ms)
Completed 200 OK in 41ms (Views: 32.7ms | ActiveRecord: 5.1ms | Allocations: 17394)
```

usersとposts、それぞれのテーブル1回ずつだけの計2本になりました。

とりあえずアプリケーションの動きとしては直ったように見えますが、実はこの状態でrspecを動かすと盛大にコケまくります。
次回はrspecとseedを確認していきます。


# 続き

→[Rails 6で認証認可入り掲示板APIを構築する #13 認証ヘッダの付与](https://qiita.com/rf_p/items/ba0348fc2271cbb8e79f)
【[連載目次へ](https://qiita.com/rf_p/items/a846553c11571f4177eb#%E7%9B%AE%E6%AC%A1)】