LoginSignup
11
5

rails+VueでCRUDをやってみた(spa)

Last updated at Posted at 2023-10-08

このサイトを読むとrails+VueでCRUDができるようになります。
多分駆け出しエンジニアさんが一番最初にバックエンドとフロントエンドで
やりたいことだと思うので、まずはやってみましょう。

環境構築はこちらから

Readはこちら

Createから行きます。

階層

├── App.vue
├── assets
│   └── vue.svg
├── components
│   └── HelloWorld.vue
├── lib
│   └── axios.ts
├── main.ts
├── router.ts
├── style.css
├── views
│   ├── BookEdit.vue
│   ├── Create.vue
│   └── Home.vue
└── vite-env.d.ts
yarn add vue-router
App.vue
<template>
  <nav>
    <router-link to="/">Home</router-link> | <router-link to="/create">Create</router-link> |
    <router-link to="/BookEdit">edit</router-link>
  </nav>
  <main>
    <router-view />
  </main>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
package.json
{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.4.0",
    "bootstrap": "^5.3.1",
    "vue": "^3.3.4",
    "vue-class-component": "^7.2.6",
    "vue-property-decorator": "^9.1.2",
    "vue-router": "^4.2.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vue-tsc": "^1.8.5"
  }
main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
router.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

export default createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: Home,
    },
    {
      path: '/create',
      component: () => import('@/views/Create.vue'),
    },
    {
      path: '/edit/:id',
      component: () => import('@/views/BookEdit.vue'),
    },
  ],
})
Home.vue
<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
    <div class="row #e3f2fd blue lighten-5">
      <div class="col s4 m6" v-for="book in books" :key="book.id">
        <div class="card btn">
          <span class="card-title" @click="setBookInfo(book.id)">
            {{ book.title }}
          </span>
        </div>
      </div>
    </div>
    <div class="row" v-show="bookInfoBool">
      <div class="col s12 m12">
        <div class="card blue-grey darken-1">
          <div class="card-content white-text">
            <span class="card-title">{{ bookInfo.title }}</span>
            <div class="detail">
              ・著者:{{ bookInfo.author }}
            </div>
            <div class="detail">
              ・出版社:{{ bookInfo.publisher }}
            </div>
            <div class="detail">
              ・ジャンル:{{ bookInfo.genre }}
            </div>
<router-link :to="{ path: `/edit/${bookInfo.id}` }" class="btn">本の編集</router-link>
<button class="btn #e53935 red darken-1" @click="deleteBook(bookInfo.id)">削除</button>
        </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import axios from 'axios';

const bookInfo = ref({});
const bookInfoBool = ref(false);
const books = ref([]);

const deleteBook = (id: number) => {
  axios.delete(`http://localhost:3000/api/books/${id}`).then(() => {
    books.value = [];
    bookInfo.value = '';
    bookInfoBool.value = false;
    fetchBooks();
  });
};
const fetchBooks = () => {
  axios.get('http://localhost:3000/api/books').then(
    (res) => {
      books.value = res.data;
    },
    (error) => {
      console.log(error);
    }
  );
};

const setBookInfo = (id: number) => {
  axios.get(`http://localhost:3000/api/books/${id}.json`).then((res) => {
    bookInfo.value = res.data;
    bookInfoBool.value = true;
  });
};

onMounted(fetchBooks);
</script>

<style scoped></style>
Create.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 />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Author" type="text" class="validate" v-model="book.author" required />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Publisher" type="text" class="validate" v-model="book.publisher" required />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Genre" type="text" class="validate" v-model="book.genre" required />
        </div>
      </div>
      <button class="btn btn-info waves-effect waves-light" @click="createBook">本を登録</button>
    </form>
  </div>
</template>

<script lang="ts">
import { ref } from 'vue';
import axios from 'axios';
import { Router, useRouter } from 'vue-router';
export default {
  setup() {
    const book = ref({
      title: '',
      author: '',
      publisher: '',
      genre: ''
    });

    const router: Router = useRouter();

    const createBook = () => {
      if (!book.value.title) return;
      axios
        .post('http://localhost:3000/api/books', { book: book.value })
        .then((res) => {
          router.push({ path: '/' });
        })
        .catch((error) => {
          console.error(error);
        });
    };

    return { book, createBook };
  }
};
</script>

<style scoped></style>
books_controller.rb
class Api::BooksController < ApplicationController

  def index
    books = Book.all

    render json: books

    # render json: {book: books}
  end

  def show
    book = Book.find(params[:id])
    render json: book
  end

  def create
    @book = Book.new(book_params)
    if @book.save
      head :no_content
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

  def update
    @book = Book.find(params[:id])
    if @book.update(book_params)
      render json: @book
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

  def destroy
    @book = Book.find(params[:id])
    if @book.destroy
      head :no_content
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end


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

完成
http://localhost:5173/create
スクリーンショット 2023-10-09 211930.png

edit

BookEdit.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 />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Author" type="text" class="validate" v-model="book.author" required />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Publisher" type="text" class="validate" v-model="book.publisher" required />
        </div>
      </div>
      <div class="row">
        <div class="input-field">
          <input placeholder="Genre" type="text" class="validate" v-model="book.genre" required />
        </div>
      </div>
      <div class="btn" @click="updateBook(book.id)">本の情報を変更</div>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios';

interface Book {
  id: string;
  title: string;
  author: string;
  publisher: string;
  genre: string;
}
export default defineComponent({
  name: 'BookEdit',
  data() {
    return {
      id: this.$route.params.id,
      book: {
        id: '',
        title: '',
        author: '',
        publisher: '',
        genre: '',
      } as Book,
    };
  },
  mounted() {
    this.setBookEdit(this.id);
  },
  methods: {
    async setBookEdit(id: string) {
      try {
        const response = await axios.get<Book>(`http://localhost:3000/api/books/${id}.json`);
        const data = response.data;
        this.book.id = data.id;
        this.book.title = data.title;
        this.book.author = data.author;
        this.book.publisher = data.publisher;
        this.book.genre = data.genre;
      } catch (error) {
        console.error(error);
      }
    },
    async updateBook(id: string) {
      if (!this.book.title) return;
      try {
        await axios.put(`http://localhost:3000/api/books/${id}`, { book: this.book });
        this.$router.push({ path: '/' });
      } catch (error) {
        console.error(error);
      }
    },
  },
});
</script>

<style scoped></style>

下記を追加

Home.vue
<router-link :to="{ path: `/edit/${bookInfo.id}` }" class="btn">本の編集</router-link>

スクリーンショット 2023-10-09 212116.png

http://localhost:5173/edit/4
スクリーンショット 2023-10-09 212326.png

delete

Home.vue
<button class="btn #e53935 red darken-1" @click="deleteBook(bookInfo.id)">削除</button>
Home.vue
const deleteBook = (id: number) => {
  axios.delete(`http://localhost:3000/api/books/${id}`).then(() => {
    books.value = [];
    bookInfo.value = '';
    bookInfoBool.value = false;
    fetchBooks();
  });
};

追加

スクリーンショット 2023-10-09 212757.png

これでCRUDはできるはずです。
見にくいかもしれませんがHome.vueがわかればわかるはず。
いろんな人に助けてもらってここまでできました。
ありがとうございます。
ここどうなっているのとかこここう書いてほしいとか要望がありましたら言ってください。

11
5
2

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
11
5