31
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Rails+Vue.js]に係るCRUD入門〜Part4: Create, Update, Delete編〜

Last updated at Posted at 2019-08-18

Rails+Vue.js+Webpackerによる「Create, Read, Update, Destroy」処理のチュートリアルを記述する。
なお,前提知識として,Railsチュートリアル終了程度を想定する。

<概要>

■ 本記事の内容

  • 新規データの登録,既存データの編集,データの削除機能を実装する。
  • 今回のコードは,GitHubのコミット履歴で確認可能である。
    • [APIの作成(Create:本の登録)]から[APIの作成(Delete:本の削除)]まで

■ 記事共通

<本文>

■ APIの作成(Create:本の登録)

○1:[books_controller.rb]の修正

app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  protect_from_forgery :except => [:create]
...
  def create
    @book = Book.new(book_params)
    if @book.save
      head :no_content
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

  private

  def book_params
    params.fetch(:book, {}).permit(
      :title, :author, :publisher, :genre
    )
  end
end

○2:[BookCreate.vue]の作成

  • createBook: function(){}
    1. [axios.post('/api/books', { book: this.book })]により,Railsに入力したデータを送信し,
    2. [this.$router.push({ path: '/' });]により,ルートページへリダイレクトする。
app/javascript/pages/BookCreate.vue
<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">本の登録</h1>
    <form class="col">
      <div class="row">
        <div class="input-field">
          <input placeholder="Title" type="text" class="validate" v-model="book.title" required="required"></br>
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Author" type="text" class="validate" v-model="book.author" required="required">
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Publisher" type="text" class="validate" v-model="book.publisher" required="required">
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Genre" type="text" class="validate" v-model="book.genre" required="required">
        </div>
      </div>
      <div class="btn btn-info waves-effect waves-light" v-on:click="createBook">本を登録</div>
    </form>
  </div>
</template>

<script>
  import axios from 'axios';
  export default {
    data: function() {
      return {
        book: {
          title: '',
          author: '',
          publisher: '',
          genre: ''
        }
      }
    },
    methods: {
      createBook: function () {
        if (!this.book.title) return;
        axios.post('/api/books', { book: this.book }).then((res) => {
          this.$router.push({ path: '/' });
        }, (error) => {
          console.log(error);
        });
      }
    }
  }
</script>

<style scoped></style>

○3:[router.js]の修正

  • 新規登録用のコンポーネントBookCreate.vueをルーティングに追加する。
app/javascript/router/router.js
import Vue          from 'vue'
import VueRouter    from 'vue-router'
import BookHome     from '../pages/BookHome.vue'
import BookCreate   from '../pages/BookCreate.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/',  name: 'BookHome', component: BookHome },
  { path: '/create',  component: BookCreate },
];

export default new VueRouter({ routes });

○4:[routes.rb]の修正

  • ルーティングにCreateアクションを追加する。
config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api do
    resources :books, only: [:index, :show, :create]
  end
end

○5:[Header.vue]の修正

  • 新規登録ページへのリンクを設定する。
app/javascript/components/Header.vue
...
<ul id="nav-mobile" class="right">
  <li><router-link to="/create">本の登録</router-link></li>
</ul>
...

○6:動作確認
 この時点で,次のように動作したら成功となる。
 なお,GIFは本稿最後に掲載する。

  1. トップページからヘッダーの[本の登録]をクリックすると,新規登録ページに遷移する。
  2. 4つのフィールドに本の情報を入力後,[本を登録]をクリックすると,トップページに遷移する。
  3. トップページの下部に登録した本が表示されている。

■ APIの作成(Update:本の編集)

○1:[books_controller.rb]の修正

  • updateアクションを追加する。
  • CSRF対策の除外設定にupdateアクションを追加する。
app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  protect_from_forgery except: [:create, :update]
...
  def update
    @book = Book.find(params[:id])
    if @book.update_attributes(book_params)
      render 'index', formats:'json', handlers: 'jbuilder'
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end
...

○2:[BookEdit.vue]の作成

  • 基本的にBookCreate.vueと同じであるが,API通信のメソッドを変更している。
  • [id: this.$route.params.id,]
    • 遷移時のパラメータである[bookInfo.id]を格納する。
  • setbookEdit(id){}
    • [bookInfo.id]を元に編集するレコードのデータをフォームに表示させる。
  • updateBook(id) {}
    • [createBook]と同じ動きをするが,HTTPメソッドがPUTであることに注意すること。
    • http://localhost:5000/rails/info/routes でルーティングを確認すると理解が深まる。
app/javascript/pages/BookCreate.vue
<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">本の編集</h1>
    <form class="col s12">
      <div class="row">
        <div class="input-field">
          <input placeholder="Title" type="text" class="validate" v-model="book.title" required="required"></br>
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Author" type="text" class="validate" v-model="book.author" required="required">
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Publisher" type="text" class="validate" v-model="book.publisher" required="required">
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Genre" type="text" class="validate" v-model="book.genre" required="required">
        </div>
      </div>
      <div class="btn" v-on:click="updateBook(book.id)">本の情報を変更</div>
    </form>
  </div>
</template>

<script>
  import axios from 'axios';
  export default {
    name: 'BookEdit',
    data: function() {
      return {
        id: this.$route.params.id,
        book: {
          id: '',
          title: '',
          author: '',
          publisher: '',
          genre: '',
        },
      }
    },
    mounted: function() {
      this.setbookEdit(this.id);
    },
    methods: {
      setbookEdit(id){
        axios.get(`api/books/${id}.json`).then(res => {
          this.book.id = res.data.id;
          this.book.title = res.data.title;
          this.book.author = res.data.author;
          this.book.publisher = res.data.publisher;
          this.book.genre = res.data.genre;
        });
      },
      updateBook(id) {
        if (!this.book.title) return;
        axios.put(`/api/books/${id}`, { book: this.book }).then((res) => {
          this.$router.push({ path: '/' });
        }, (error) => {
          console.log(error);
        });
      },
    }
  }
</script>

<style scoped></style>

○3:[BookHome.vue]の修正

  • 本の詳細情報表示欄に[本の編集]ボタンを設置して,BookEditのリンクを作成する。
app/javascript/pages/BookHome.vue
...
<div class="detail">
  ・ジャンル:{{ bookInfo.genre }}
</div>
<router-link :to="{ path: `/edit/${bookInfo.id}` }" class="btn">本の編集</router-link>
...

○4:[router.js]の修正

  • BookEditをルーティングに組み込む。
app/javascript/router/router.js
import Vue          from 'vue'
import VueRouter    from 'vue-router'
import BookHome     from '../pages/BookHome.vue'
import BookCreate   from '../pages/BookCreate.vue'
import BookEdit     from '../pages/BookEdit.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/',          name: 'BookHome',    component: BookHome },
  { path: '/create',    name: 'BookCreate',  component: BookCreate },
  { path: '/edit/:id',  name: 'BookEdit',    component: BookEdit },
];

export default new VueRouter({ routes });

○5:[routes.rb]の修正

  • updateアクションを追加する。
config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api do
    resources :books, only: [:index, :show, :create, :update]
  end
end

○6:動作確認
 この時点で,次のように動作したら成功となる。
 なお,GIFは本稿最後に掲載する。

  1. トップページから本のタイトルをクリックすると,ページ下部に本のデータが表示される。
  2. [本の編集]ボタンをクリックすると,本の編集ページに遷移する。
  3. 編集したいデータを入力後,[本の情報を変更]ボタンをクリックすると,トップページに遷移する。
  4. 本のデータが変更されている。

■ APIの作成(Delete:本の削除)

○1:[books_controller.rb]の修正

app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  protect_from_forgery except: [:create, :update, :destroy]
...
  def destroy
    @book = Book.find(params[:id])
    if @book.destroy
      head :no_content
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end
...

○2:[BookHome.vue]の修正

  • [削除]ボタンを設置する。
  • [削除]ボタンとトリガーとした,deleteBookメソッドを作成する。
    • 次のコードのようにページを再読込する手段でも良いし,booksから対象データのみを削除する手段でも良い。
    • [this.$router.go({path: this.$router.currentRoute.path, force: true});]
app/javascript/pages/BookHome.vue
...
<router-link :to="{ path: `/edit/${bookInfo.id}` }" class="btn">本の編集</router-link>
<button class="btn #e53935 red darken-1" v-on:click="deleteBook(bookInfo.id)">削除</button>
...
<script>
...
methods: {
  ...
  },
  deleteBook(id) {
    axios.delete(`/api/books/${id}`).then(res => {
      this.books = [];
      this.bookInfo = '';
      this.bookInfoBool = false;
      this.fetchBooks();
    })
  },
...

○3:[routes.rb]の修正

  • destroyアクションを追加する。
config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api do
    resources :books, only: [:index, :show, :create, :update, :destroy]
  end
end

○4:動作確認
 この時点で,次のように動作したら成功となる。

  1. トップページから本のタイトルをクリックすると,ページ下部に本のデータが表示される。
  2. [削除]ボタンをクリックすると,対象のデータが一覧から削除されている。

■ Part1~4までの動作確認

04_CreateUpdateDelete:動作確認.gif

〜Part4: Create, Update, Delete編終了〜
〜Part5: Vuex設定編へ進む〜

31
18
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
31
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?