5
5

More than 3 years have passed since last update.

~part4~【FC版】React + Rails API + axios + react-router-domでCRUDを実装する

Posted at

こんにちは!スージです。
こちらの記事の続きです

やりたい事

  • 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.idpost.user_idが一致した場合に更新する条件を追加
  • destroyアクション内にcurrent_user.idpost.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件のデータが取得できています
スクリーンショット 2021-08-16 10.53.41.png

一覧画面(List.jsx)では全件(4件)データが取得できています
スクリーンショット 2021-08-16 10.54.09.png

レイアウトが正しく表示されていればOK
/(Home)
スクリーンショット 2021-08-16 11.01.56.png

/user/post(ユーザー一覧)
スクリーンショット 2021-08-16 11.02.03.png

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'),
    },
  });
};

createupdatedeleteのリクエストを送る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導入をやってみようと思っています

おわり
5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5