Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post

Building a bulletin board API with certification authorization in Rails 6 # 11 User model test and validation added

Associate from post to user

Associate post and user. Assuming that the intended reader has completed the Rails tutorial, I will omit the explanation of the meaning, but let's add belongs_to: user to post and has_many: posts to user.

$ rails g migration AddUserIdToPosts user:references

If there is a record, it will be caught in the not null constraint and migration will result in an error, so db: reset will occur (rough)

$ rails db:reset
$ rails db:migrate

app/models/post.rb


...
 class Post < ApplicationRecord
+  belongs_to :user
+
...

app/models/user.rb


...
   include DeviseTokenAuth::Concerns::User

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

After associating the two tables, try experimenting with rails c to see if it works.

$ rails  c
[1] pry(main)> user = User.create!(name: "hoge", email: "[email protected]",  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: "[email protected]", name: "hoge", email: "[email protected]", created_at: "2020-09-08 08:36:11", updated_at: "2020-09-08 08:36:11">

Apparently, you can safely call posts from user and user from post.

fix post serializer

I would like to get the user's ID, name and email address from the posts API. At that time, the serializer and controller should be fixed. At the very least, you only need serializer to work, but please be aware that if you do not modify the controller, a large amount of useless SQL called N + 1 problem will flow and performance will drop.

app/serializers/post_serializer.rb


...
 class PostSerializer < ActiveModel::Serializer
   attributes :id, :subject, :body
+  belongs_to :user

This will make the user stick together.

$ curl localhost:8080/v1/posts/1
{"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"provider":"email","uid":"[email protected]","name":"hoge","email":"[email protected]","created_at":"2020-09-08T08:36:11.972Z","updated_at":"2020-09-08T08:36:11.972Z"}}}

It's good that they're stuck together, but I've got a lot of unnecessary information from the user. There is no serializer for user, so let's add it.

Create user serializer

The serializer is automatically generated when the model is created, but since the model was created with devise_token_auth, the command is manually hit. In addition, activeModelSerializer does not work for the response json of the controller automatically generated by devise_token_auth. If you want to enable it, you need to override the devise controller, but I will omit it this time.

From now on user from post model

$ rails g serializer user

app/serializers/user_serializer.rb


# frozen_string_literal: true

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :email
end
$ curl localhost:8080/v1/posts/1
{"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"name":"hoge","email":"[email protected]"}}}

This is OK for the time being.

Dealing with N + 1 problems

Now, let's create multiple user / multiple post data with rails c and hit curl localhost: 8080 / v1 / posts. I can get the data safely, but when I move to the terminal launched with rails s ...

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)

Although omitted, a large amount of SQL is flowing like this. This is the N + 1 problem.

Since the user associated with post is found and fetched one record at a time, a large amount of useless SQL flows.

This is OK if you include it at the timing when Post.all comes.

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
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)

There are now two tables, one for users and one for posts.

For the time being, the behavior of the application seems to have been fixed, but in fact, if you move rspec in this state, it will moss grandly. Next time, I will check rspec and seed.

Continued

Building a bulletin board API with authentication authorization in Rails 6 # 13 Adding authentication header [To the serial table of contents]

Recommended Posts

Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post
Build a bulletin board API with authentication and authorization with Rails # 18 ・ Implementation of final user controller
Build a bulletin board API with authentication authorization in Rails 6 # 11 User model test and validation added
Build a bulletin board API with authentication and authorization with Rails 6 # 1 Environment construction
Build a bulletin board API with authentication authorization in Rails # 13 Add authentication header
Building a bulletin board API with authentication authorization with Rails 6 Validation and test implementation of # 4 post
Build a bulletin board API with authentication authorization with Rails 6 # 3 RSpec, FactoryBot introduced and post model
Build a bulletin board API with authentication authorization in Rails 6 # 5 controller, routes implementation
Introduced # 10 devise_token_auth to build a bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization with Rails 6 # 2 Introducing git and rubocop
Introducing # 15 pundit to build a bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails # 17 Add administrator privileges
Build a bulletin board API with authentication authorization in Rails 6 # 14 seed Execution time display
# 16 policy setting to build bulletin board API with authentication authorization in Rails 6
Introduced # 9 serializer to build bulletin board API with authentication authorization in Rails 6
# 6 show, create implementation to build bulletin board API with authentication authorization in Rails 6
# 7 update, destroy implementation to build bulletin board API with authentication authorization in Rails 6
[Rails] Get access_token at the time of Twitter authentication with Sorcery and save it in DB
A note of what I stumbled upon and noticed in catching up with Laravel from Rails
[Implementation procedure] Create a user authentication function using sorcery in Rails
I implemented Rails API with TDD by RSpec. part2 -user authentication-
I tried to make a group function (bulletin board) with Rails
Make a daily build of the TOPPERS kernel with Gitlab and Docker
[Rails 6] Register and log in with Devise + SNS authentication (multiple links allowed)
Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)
How to get the ID of a user authenticated with Firebase in Swift
Get a list of Qiita articles for a specific user with Ruby + Qiita API v2
Validate the identity token of a user authenticated with AWS Cognito in Java
Create a simple bulletin board with Java + MySQL
(Basic authentication) environment variables in rails and Docker
Try to create a bulletin board in Java
[Rails] Ranking and pagination in order of likes
[Rails] [Docker] Copy and paste is OK! How to build a Rails development environment with Docker