こんにちは!スージです。
こちらの記事の続きです
やりたい事
-
Userモデル
とPostモデル
を1対多で関連付け - ユーザーが登録した投稿を一覧画面に表示
- 新規登録した時に
user_id
を保存 - ログイン中のユーザーは自分が投稿したデータのみ
更新
・削除
ができる
開発環境
Ruby 2.7.2
Rails 6.1.4
MySQL
node.js 16.6.2
React 17.0.2
参考
Rails deviseで使えるようになるヘルパーメソッド一覧
Rails側から実装開始
まず、postテーブルに外部キーuser_id
を追加します
backend $ rails g migration AddColumnsToPosts
# 日付_add_columns_to_posts.rb
class AddColumnsToPosts < ActiveRecord::Migration[6.1]
def change
add_reference :posts, :user, foreign_key: true, after: :neko_type
end
end
rails db:migrate
user_id
カラムが追加されました
schema.rb
create_table "posts", charset: "utf8mb4", force: :cascade do |t|
t.string "name"
t.string "neko_type"
t.bigint "user_id" # 追加した外部キー
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id"], name: "index_posts_on_user_id"
end
アソシエーション
# user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
include DeviseTokenAuth::Concerns::User
has_many :posts, dependent: :destroy #追加
end
# post.rb
class Post < ApplicationRecord
belongs_to :user # 追加
has_one :detail_info, dependent: :destroy
end
ルーティング
# routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts
resources :users # 追加
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
registrations: 'api/v1/auth/registrations'
}
namespace :auth do
resources :sessions, only: %i[index]
end
end
end
end
コントローラ作成
rails g controller api/v1/users
showアクションに処理を書きます。ログイン中のユーザーが投稿したpostと紐づくdetail_infoを取得できるようにしておきます
#users_controller.rb
class Api::V1::UsersController < ApplicationController
def show
render json: Post.where(user_id: params[:id]).to_json(include: :detail_info)
end
end
ダミーデータ修正
最終的に画面で確認した時に分かりやすいようにユーザーを2人登録します。
# seeds.rb
User.create!(name: 'test1', email: 'test@example.com', password: 'password', password_confirmation: 'password')
User.create!(name: 'test2', email: 'test2@example.com', password: 'password', password_confirmation: 'password')
user1 = User.find(1)
post1 = Post.create!(name: 'ニャア', neko_type: 'アメリカンショートヘア', user: user1)
DetailInfo.create!(post: post1, favorite_food: '魚', favorite_toy: '猫じゃらし')
post2 = Post.create!(name: 'まる', neko_type: 'スコッティシュフォールド', user: user1)
DetailInfo.create!(post: post2, favorite_food: '野菜', favorite_toy: 'まりたん')
user2 = User.find(2)
post3 = Post.create!(name: 'むぎ', neko_type: 'スコッティシュフォールド', user: user2)
DetailInfo.create!(post: post3, favorite_food: '肉', favorite_toy: 'ダンボール')
post4 = Post.create!(name: 'ばに', neko_type: 'エキゾチックショートヘア', user: user2)
DetailInfo.create!(post: post4, favorite_food: '豆', favorite_toy: 'キャットタワー')
ダミーデータを入れ直します
rails db:migrate:reset
rails db:seed
curlコマンドでデータを取得できる事を確認します
// user_id: 1
curl http://localhost:5000/api/v1/users/1
[{"id":1,"name":"ニャア","neko_type":"アメリカンショートヘア","user_id":1,"created_at":"2021-08-14T13:48:21.707Z","updated_at":"2021-08-14T13:48:21.707Z",
"detail_info":{"id":1,"post_id":1,"favorite_food":"魚","favorite_toy":"猫じゃらし","created_at":"2021-08-14T13:48:21.717Z","updated_at":"2021-08-14T13:48:21.717Z"}},
{"id":2,"name":"まる","neko_type":"スコッティシュフォールド","user_id":1,"created_at":"2021-08-14T13:48:21.719Z","updated_at":"2021-08-14T13:48:21.719Z",
"detail_info":{"id":2,"post_id":2,"favorite_food":"野菜","favorite_toy":"まりたん","created_at":"2021-08-14T13:48:21.720Z","updated_at":"2021-08-14T13:48:21.720Z"}}]
// user_id: 2
[{"id":3,"name":"むぎ","neko_type":"スコッティシュフォールド","user_id":2,"created_at":"2021-08-14T13:48:21.722Z","updated_at":"2021-08-14T13:48:21.722Z",
"detail_info":{"id":3,"post_id":3,"favorite_food":"肉","favorite_toy":"ダンボール","created_at":"2021-08-14T13:48:21.723Z","updated_at":"2021-08-14T13:48:21.723Z"}},
{"id":4,"name":"ばに","neko_type":"エキゾチックショートヘア","user_id":2,"created_at":"2021-08-14T13:48:21.725Z","updated_at":"2021-08-14T13:48:21.725Z",
"detail_info":{"id":4,"post_id":4,"favorite_food":"豆","favorite_toy":"キャットタワー","created_at":"2021-08-14T13:48:21.731Z","updated_at":"2021-08-14T13:48:21.731Z"}}]
作成したユーザー2人に紐づくposts
と、postに紐づくdetail_info
がそれぞれ2件づつ取得できています
postsコントローラの修正
フロントで制御しますが、自分の投稿以外を更新
、削除
できないように処理を修正します
# application_controller.rb
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
skip_before_action :verify_authenticity_token
helper_method :current_user, :user_signed_in?, :authenticate_user!
end
devise
のヘルパーメソッドauthenticate_user!
を追加します
# posts_controller.rb
class Api::V1::PostsController < ApplicationController
before_action :authenticate_api_v1_user!, only: [:create, :update, :destroy] # 追加
def index
# 省略
end
def show
# 省略
end
def create
# 省略
end
def update
post = Post.find(params[:id])
detail_info = DetailInfo.find_by(post_id: params[:id])
if current_api_v1_user.id == post.user_id # 追加
ActiveRecord::Base.transaction do
if post.update(post_params) && detail_info.update(detail_info_params)
render json: post.to_json(include: :detail_info)
else
render json: { post: post.errors, detail_info: detail_info.errors }, status: 422
end
end
else
render json: { message: 'can not update data' }, status: 422
end
end
def destroy
post = Post.find(params[:id])
if current_api_v1_user.id == post.user_id # 追加
post.destroy
render json: post.to_json(include: :detail_info)
else
render json: { message: 'can not delete data' }, status: 422
end
end
private
def post_params
params.require(:post).permit(:name, :neko_type).merge(user_id: current_api_v1_user.id) # merge部分を追加
end
def detail_info_params
# 省略
end
end
-
before_action
を追加 -
update
アクション内にcurrent_user.id
とpost.user_id
が一致した場合に更新する条件を追加 -
destroy
アクション内にcurrent_user.id
とpost.user_id
が一致した場合に更新する条件を追加 - 新規作成する時にuser_idを一緒に保存できるように、ストロングパラメータに
.merge(user_id: current_api_v1_user.id)
を追加
次にReact側を実装
Rails側で追加したエンドポイントにリクエストを投げるクライアントを作成します
frontend $ touch /src/lib/api/user.js
// /src/lib/api/user.js
import client from './client';
import Cookies from 'js-cookie';
export const getUserPosts = (id) => {
return client.get(`/users/${id}`, {
headers: {
'access-token': Cookies.get('_access_token'),
client: Cookies.get('_client'),
uid: Cookies.get('_uid'),
},
});
};
画面を用意する
その前に、ひと手間あります。一覧画面(List.jsx)と同じテーブル構造のレイアウトにするのでList.jsx
で記述したレイアウトをコンポーネントに切り出して、共通で使用します
touch /src/components/commons/ListTable.jsx
// /src/components/commons/ListTable.jsx
import React from 'react';
import { Link } from 'react-router-dom';
// style
import {
Button,
TableContainer,
Table,
TableBody,
TableCell,
TableRow,
TableHead,
Paper,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
// functions
import { subString } from '../../functions/functions';
const useStyles = makeStyles({
table: {
minWidth: 650,
},
fontWeight: {
fontWeight: 900,
},
});
const ListTable = (props) => {
const classes = useStyles();
const { dataList, handleDelete, currentUser } = props;
return (
<TableContainer component={Paper}>
<Table className={classes.table} aria-label='simple table'>
<TableHead>
<TableRow>
<TableCell align='center' className={classes.fontWeight}>
名前
</TableCell>
<TableCell align='center' className={classes.fontWeight}>
猫種
</TableCell>
<TableCell align='center' className={classes.fontWeight}>
好きな食べ物
</TableCell>
<TableCell align='center' className={classes.fontWeight}>
好きなおもちゃ
</TableCell>
<TableCell align='center'></TableCell>
<TableCell align='center'></TableCell>
<TableCell align='center'></TableCell>
</TableRow>
</TableHead>
<TableBody>
{dataList.map((item, index) => (
<TableRow key={index}>
<TableCell align='center'>{subString(item.name, 15)}</TableCell>
<TableCell align='center'>
{subString(item.nekoType, 15)}
</TableCell>
<TableCell align='center'>
{subString(item.detailInfo.favoriteFood, 10)}
</TableCell>
<TableCell align='center'>
{subString(item.detailInfo.favoriteToy, 10)}
</TableCell>
{currentUser.id === item.userId ? (
<TableCell align='center'>
<Link to={`/edit/${item.id}`}>更新</Link>
</TableCell>
) : (
<TableCell align='center'></TableCell>
)}
<TableCell align='center'>
<Link to={`/post/${item.id}`}>詳細へ</Link>
</TableCell>
{currentUser.id === item.userId ? (
<TableCell align='center'>
<Button
variant='contained'
color='secondary'
onClick={() => handleDelete(item)}
>
削除
</Button>
</TableCell>
) : (
<TableCell align='center'></TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
export default ListTable;
- 親コンポーネントからは
dataList
,handleDelete
,currentUser
を受け取ります -
currentUser.id === item.userId ? 処理A : 処理B
で分岐して、自分の投稿以外は編集
・削除
のリンク・ボタンがそれぞれ表示されないよう制御します
List.jsxを修正
テーブル部分を子コンポーネントに切り出したので、修正します
// /src/components/List.jsx
import React, { useEffect, useState, useContext } from 'react';
import { getList, deletePost } from '../lib/api/post';
import { useHistory } from 'react-router-dom';
// style
import { Button } from '@material-ui/core';
// component
import ListTable from './commons/ListTable';
import SpaceRow from './commons/SpaceRow';
// context
import { AuthContext } from '../App';
const List = () => {
const { loading, isSignedIn, setIsSignedIn, currentUser } =
useContext(AuthContext);
const [dataList, setDataList] = useState([]);
useEffect(() => {
handleGetList();
}, []);
const handleGetList = async () => {
try {
const res = await getList();
console.log(res.data);
setDataList(res.data);
} catch (e) {
console.log(e);
}
};
const history = useHistory();
const handleDelete = async (item) => {
console.log('click', item.id);
try {
const res = await deletePost(item.id);
console.log(res.data);
handleGetList();
} catch (e) {
console.log(e.response);
}
};
return (
<>
<h1>HOME</h1>
<Button
variant='contained'
color='primary'
onClick={() => history.push('/new')}
>
新規作成
</Button>
<SpaceRow height={20} />
<ListTable
dataList={dataList}
handleDelete={handleDelete}
currentUser={currentUser}
/>
</>
);
};
export default List;
UserPost.jsxを作成
touch /src/components/users/UserPost.jsx
// /src/components/users/UserPost.jsx
import React, { useContext, useState, useEffect } from 'react';
import { Redirect, useHistory } from 'react-router-dom';
// style
import { Button } from '@material-ui/core';
// api
import { getUserPosts } from '../../lib/api/user';
import { deletePost } from '../../lib/api/post';
// context
import { AuthContext } from '../../App';
// component
import SpaceRow from '../commons/SpaceRow';
import ListTable from '../commons/ListTable';
const UserPost = () => {
const { loading, isSignedIn, currentUser } = useContext(AuthContext);
const [userPosts, setUserPosts] = useState({});
const history = useHistory();
useEffect(() => {
handleGetUserPosts();
}, [currentUser]);
const handleGetUserPosts = async () => {
if (!loading) {
if (isSignedIn) {
const res = await getUserPosts(currentUser.id);
console.log(res.data);
setUserPosts(res.data);
} else {
<Redirect to='/signin' />;
}
}
};
const handleDelete = async (item) => {
console.log('click', item.id);
try {
const res = await deletePost(item.id);
console.log(res.data);
handleGetUserPosts();
} catch (e) {
console.log(e);
}
};
const UserTable = () => {
if (userPosts.length >= 1) {
return (
<ListTable
dataList={userPosts}
handleDelete={handleDelete}
currentUser={currentUser}
/>
);
} else {
return <h2>投稿はありません。</h2>;
}
};
return (
<>
<h1>{currentUser.name}の投稿一覧</h1>
<Button
variant='contained'
color='primary'
onClick={() => history.push('/')}
>
戻る
</Button>
<SpaceRow height={20} />
<UserTable />
</>
);
};
export default UserPost;
ルーターに追加
// src/App.jsx
import React, { useState, useEffect, createContext } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom';
import Cookies from 'js-cookie';
// component
import List from './components/List';
import New from './components/New';
import Detail from './components/Detail';
import Edit from './components/Edit';
import Header from './components/commons/Header';
import SignUp from './components/users/SignUp';
import SignIn from './components/users/SignIn';
import MainContainer from './components/layout/MainContainer';
import UserPost from './components/users/UserPost';
// style
import { CssBaseline } from '@material-ui/core';
import { StylesProvider, ThemeProvider } from '@material-ui/styles';
import { theme } from './styles/theme';
import { getCurrentUser } from './lib/api/auth';
export const AuthContext = createContext();
const App = () => {
const [loading, setLoading] = useState(true);
const [isSignedIn, setIsSignedIn] = useState(false);
const [currentUser, setCurrentUser] = useState({});
const handleGetCurrentUser = async () => {
try {
const res = await getCurrentUser();
if (res?.data.isLogin === true) {
setIsSignedIn(true);
setCurrentUser(res?.data.data);
} else {
console.log('no current user');
// token有効期限が切れている場合、古いcookieを全て削除
Cookies.remove('_access_token');
Cookies.remove('_client');
Cookies.remove('_uid');
}
} catch (e) {
console.log(e);
}
setLoading(false);
};
useEffect(() => {
handleGetCurrentUser();
}, [setCurrentUser]);
const Private = ({ children }) => {
if (!loading) {
if (isSignedIn) {
return children;
} else {
return <Redirect to='/signin' />;
}
} else {
return <></>;
}
};
return (
<>
<StylesProvider injectFirst>
<ThemeProvider theme={theme}>
<AuthContext.Provider
value={{
loading,
setLoading,
isSignedIn,
setIsSignedIn,
currentUser,
setCurrentUser,
}}
>
<CssBaseline />
<Router>
<Header />
<MainContainer>
<Switch>
<Route exact path='/signup' component={SignUp} />
<Route exact path='/signin' component={SignIn} />
<Private>
<Route exact path='/' component={List} />
<Route path='/post/:id' component={Detail} />
<Route exact path='/new' component={New} />
<Route path='/edit/:id' component={Edit} />
{/* 追加 */}
<Route exact path='/user/posts' component={UserPost} />
</Private>
</Switch>
</MainContainer>
</Router>
</AuthContext.Provider>
</ThemeProvider>
</StylesProvider>
</>
);
};
export default App;
ユーザー一覧画面へのリンク
ドロワー内のリンクを修正します
// /src/components/Header.jsx
// 38行目あたり
const drawerItem = [
{ label: '一覧へ戻る', path: '/' },
{ label: '新規作成', path: '/new' },
{ label: '自分の投稿', path: '/user/posts' }, // 修正
];
動作確認
ログイン中のユーザーに紐づくデータだけ取れていればOK
ユーザー一覧画面(UserPost.jsx)にて、user_id
:1
では2件のデータが取得できています
一覧画面(List.jsx)では全件(4件)データが取得できています
apiクライアントにheader
を追加
こちらの記事で実装したapiクライアントを修正します。
具体的には、Rails側のdeviseのヘルパーメソッドauthenticate_user!
・current_user
を使ってユーザー認証をするので、header
にユーザー情報を載せてリクエストを投げる必要があります。
// /src/lib/api/post.js
import client from './client';
import Cookies from 'js-cookie';
// 一覧
export const getList = () => {
return client.get('/posts');
};
// 詳細
export const getDetail = (id) => {
return client.get(`/posts/${id}`);
};
// 新規作成
// header情報を追加
export const createPost = (params) => {
return client.post('/posts', params, {
headers: {
'access-token': Cookies.get('_access_token'),
client: Cookies.get('_client'),
uid: Cookies.get('_uid'),
},
});
};
// 更新
// header情報を追加
export const updatePost = (id, params) => {
return client.patch(`/posts/${id}`, params, {
headers: {
'access-token': Cookies.get('_access_token'),
client: Cookies.get('_client'),
uid: Cookies.get('_uid'),
},
});
};
// 削除
// header情報を追加
export const deletePost = (id) => {
return client.delete(`/posts/${id}`, {
headers: {
'access-token': Cookies.get('_access_token'),
client: Cookies.get('_client'),
uid: Cookies.get('_uid'),
},
});
};
create
・update
・delete
のリクエストを送るapiクライアントにヘッダー情報を追加しました
ログインユーザーで新規登録
・更新
・削除
ができればOK
create
11:17:45 web.1 | Started POST "/api/v1/posts" for ::1 at 2021-08-16 11:17:45 +0900
11:17:45 web.1 | Processing by Api::V1::PostsController#create as HTML
11:17:45 web.1 | Parameters: {"name"=>"test", "neko_type"=>"test", "detail_info"=>{"favorite_food"=>"test", "favorite_toy"=>"test"}, "post"=>{"name"=>"test", "neko_type"=>"test"}}
11:17:45 web.1 | TRANSACTION (0.2ms) BEGIN
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:54:in `post_params'
11:17:45 web.1 | User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`uid` = 'test@example.com' LIMIT 1
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:54:in `post_params'
11:17:45 web.1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:16:in `block in create'
11:17:45 web.1 | Post Create (0.3ms) INSERT INTO `posts` (`name`, `neko_type`, `user_id`, `created_at`, `updated_at`) VALUES ('test', 'test', 1, '2021-08-16 02:17:45.431151', '2021-08-16 02:17:45.431151')
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:16:in `block in create'
11:17:45 web.1 | DetailInfo Create (0.2ms) INSERT INTO `detail_infos` (`post_id`, `favorite_food`, `favorite_toy`, `created_at`, `updated_at`) VALUES (5, 'test', 'test', '2021-08-16 02:17:45.432983', '2021-08-16 02:17:45.432983')
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:16:in `block in create'
11:17:45 web.1 | TRANSACTION (1.1ms) COMMIT
11:17:45 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:13:in `create'
11:17:45 web.1 | User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
11:17:45 web.1 | Completed 200 OK in 13ms (Views: 0.1ms | ActiveRecord: 2.9ms | Allocations: 5071)
update
11:18:58 web.1 | Started PATCH "/api/v1/posts/1" for ::1 at 2021-08-16 11:18:58 +0900
11:18:58 web.1 | Processing by Api::V1::PostsController#update as HTML
11:18:58 web.1 | Parameters: {"name"=>"ニャアupdate", "neko_type"=>"アメリカンショートヘアupdate", "detail_info"=>{"favorite_food"=>"魚update", "favorite_toy"=>"猫じゃらしupdate"}, "id"=>"1", "post"=>{"name"=>"ニャアupdate", "neko_type"=>"アメリカンショートヘアupdate"}}
11:18:58 web.1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`uid` = 'test@example.com' LIMIT 1
11:18:58 web.1 | Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:25:in `update'
11:18:58 web.1 | DetailInfo Load (0.2ms) SELECT `detail_infos`.* FROM `detail_infos` WHERE `detail_infos`.`post_id` = 1 LIMIT 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:26:in `update'
11:18:58 web.1 | TRANSACTION (0.1ms) BEGIN
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:30:in `block in update'
11:18:58 web.1 | User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:30:in `block in update'
11:18:58 web.1 | Post Update (0.5ms) UPDATE `posts` SET `posts`.`name` = 'ニャアupdate', `posts`.`neko_type` = 'アメリカンショートヘアupdate', `posts`.`updated_at` = '2021-08-16 02:18:58.901940' WHERE `posts`.`id` = 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:30:in `block in update'
11:18:58 web.1 | Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:30:in `block in update'
11:18:58 web.1 | DetailInfo Update (0.4ms) UPDATE `detail_infos` SET `detail_infos`.`favorite_food` = '魚update', `detail_infos`.`favorite_toy` = '猫じゃらしupdate', `detail_infos`.`updated_at` = '2021-08-16 02:18:58.908105' WHERE `detail_infos`.`id` = 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:30:in `block in update'
11:18:58 web.1 | DetailInfo Load (0.5ms) SELECT `detail_infos`.* FROM `detail_infos` WHERE `detail_infos`.`post_id` = 1 LIMIT 1
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:31:in `block in update'
11:18:58 web.1 | TRANSACTION (0.8ms) COMMIT
11:18:58 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:29:in `update'
11:18:58 web.1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
11:18:58 web.1 | Completed 200 OK in 25ms (Views: 0.3ms | ActiveRecord: 4.1ms | Allocations: 7453)
destroy
11:19:27 web.1 | Started DELETE "/api/v1/posts/5" for ::1 at 2021-08-16 11:19:27 +0900
11:19:27 web.1 | Processing by Api::V1::PostsController#destroy as HTML
11:19:27 web.1 | Parameters: {"id"=>"5"}
11:19:27 web.1 | User Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`uid` = 'test@example.com' LIMIT 1
11:19:27 web.1 | Post Load (0.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 5 LIMIT 1
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:42:in `destroy'
11:19:27 web.1 | TRANSACTION (0.1ms) BEGIN
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:44:in `destroy'
11:19:27 web.1 | DetailInfo Load (0.3ms) SELECT `detail_infos`.* FROM `detail_infos` WHERE `detail_infos`.`post_id` = 5 LIMIT 1
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:44:in `destroy'
11:19:27 web.1 | DetailInfo Destroy (0.2ms) DELETE FROM `detail_infos` WHERE `detail_infos`.`id` = 5
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:44:in `destroy'
11:19:27 web.1 | Post Destroy (0.2ms) DELETE FROM `posts` WHERE `posts`.`id` = 5
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:44:in `destroy'
11:19:27 web.1 | TRANSACTION (0.2ms) COMMIT
11:19:27 web.1 | ↳ app/controllers/api/v1/posts_controller.rb:44:in `destroy'
11:19:27 web.1 | User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
11:19:27 web.1 | Completed 200 OK in 13ms (Views: 0.1ms | ActiveRecord: 2.3ms | Allocations: 5164)
最後に
part1 ~ part4をReact × Rail APIアプリケーションの基本的な処理を一通り記事にまとめてみました
次はredux × redux sagaのステート管理とTypeScript導入をやってみようと思っています