Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Vue、Vuex、Railsを使用したフルスタックアプリケーションの構築

スケーラビリティを念頭に置いてフルスタックアプリケーションを構築することは、特に、完全なタイプスクリプトをサポートする最新バージョンのVueおよびVuexを使用して構築する場合、威圧的になる可能性があります。この記事では、不健康な家畜への治療の処方を管理するCRUDアプリケーションを探索することで、APIリクエストとデータベースの相互作用を処理するVuex4.0を使用した状態管理からスケーラブルなフルスタックアプリケーションを構築するために知っておく必要のあるすべてを読者に教えます。バックエンドはRailsで構築され、フロントエンドによる統合のために基本的なCRUDAPIを公開します。

ほとんどの企業は、APIベースの開発アーキテクチャを採用しています。これにより、フロントエンド開発チームは、開発に適したフロントエンドツールをより柔軟に選択できるようになります。これは最善のオプションではないかもしれませんが、信頼できるオプションであり、チームの柔軟性が高まるため、多くの企業がこの開発アーキテクチャを採用しています。

前提条件

始める前に、このチュートリアルに遅れないように、次のものがあることを確認してください。

  • Rails V6.x
  • Node.js V10.x
  • RubyonRailsの実用的な知識
  • TypeScriptの実用的な知識
  • Vue.jsの実用的な知識
何を構築するか

Vue、Vuex、Railsを使用したフルスタックアプリケーションの構築

このチュートリアルでは、不健康な家畜の治療の処方を管理するフルスタックのCRUDアプリケーションを構築します。ユーザーは、家畜の処方箋を作成、更新、および削除できるようになります。 CRUD APIはRailsを使用して構築され、フロントエンドによる統合のためにCRUD APIを公開します。フロントエンドは、Vue3で構築されます。これらのスタックはそれぞれ独立したアプリケーションとして異なるポートで実行されます。

フルスタックアプリの基本アーキテクチャ

フルスタックアプリケーションは、独立して実行されるクライアントアプリケーションとサーバーアプリケーションの両方で構成され、クライアントアプリケーションの各コンポーネントは、アプリケーションの状態を適切に管理するために、Vuexを介してサーバーアプリケーションによって発行されたCRUDAPIと対話します。バックエンドアプリケーションは、CRUD APIをフロントエンドアプリケーションに公開しながら、すべての処方データをSqlite3データベースに保存します。

バックエンドサービスのセットアップ

好みのフォルダーで、次のコマンドを実行してRailsアプリを作成します。

rails new vet_clinic_api --api

これにより、RailsはこのプロジェクトをAPIとして作成するように指示され、それによってすべてのフロントエンドの依存関係(ファイルの表示)が削除されます。

データベース構成

RailsアプリケーションのデフォルトデータベースであるSqlite3を使用します。

rails g scaffold prescriptions vet_prescription:text prescribed_by:text disease:text livestock:text completed:boolean

上記のコマンドを実行することで、Railsは移行、テスト、モデル、コントローラー、ルートの初期構造をスキャフォールディングします:

rails db:migrate

このコマンドは、テーブルをデータベースに追加します。

データベースのシード

データベースに処方データをシードしてみましょう。以下のコードスニペットをdb/migrate/seed.rbに追加します。

//db/migrate/seed.rb

Prescription.destroy_all
Prescription.create!([{
    vet_prescription:"Achyranthes aspera",
    prescribed_by:"Dr Chucks",
    disease:"Rabbies",
    livestock:"goat",
    completed:false
},
{
    vet_prescription:"Achyranthes aspera",
    prescribed_by:"Dr Rex",
    disease:"Rabbies",
    livestock:"Dog",
    completed:false
},
{
    vet_prescription:"ethnovet",
    prescribed_by:"Dr Chucks",
    disease:"Pox",
    livestock:"Sheep",
    completed:false
}])
p "Created #{Prescription.count} prescriptions"

このファイルには、データベースをシードするための初期データが保存されるため、アプリの起動時に既存の処方データがいくつかあります。

次のコマンドを実行して、db/migrate/seed.rbのコードを実行します 、事前定義された処方データをデータベースにシードします:

rails db:seed

これらのいくつかのコマンドを使用して、Railsを使用して機能的なCRUDAPIを作成しました。どれくらい簡単でしたか? (笑)

CORS構成

CRUD APIをフロントエンドに公開し、フロントエンドサーバーとバックエンドサーバーの両方が異なるポートで実行されるため、RailsバックエンドでCORS構成を設定して、フロントエンドとバックエンド。

Gemfileを見つけます プロジェクトルートで、次のコード行のコメントを解除します。

# gem 'rack-cors'

次のコードをconfig/environments/initializers/cors.rbに追加します :

//config/environments/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

上記のスニペットを使用すると、任意のポートで実行されているフロントエンドアプリからRailsCRUDAPIにアクセスできます。

次のコマンドを実行して、Railsサーバーを起動します。

rails s

localhost:3000/prescriptionsに移動します すべての処方箋のJSON応答を取得します。

プロジェクトのワークフローを頻繁に変更するクライアントと協力している場合は、Railsの使用を検討してください。 Railsを使用すると、いくつかのコマンドと数行のコードで機能を簡単に実装できます。これは私の個人的な意見です。

Vueの紹介

Vueはプログレッシブフレームワークです ユーザーインターフェイスを構築するため。 Vueは、仮想DOM、レンダリング機能、サーバー側のレンダリング機能などの概念を導入して、Webアプリケーションのパフォーマンスを大幅に最適化します。

Vue 3には、開発者向けの多くの新機能と変更が含まれています。これらの機能は、フレームワークの全体的な安定性、およびその速度と保守性を向上させるように設計されています。

Vue 3で最も期待されている機能の1つであるcompositionAPIを利用します。これは、コードを記述するためのより体系的で効率的なアプローチと、完全なTypeScriptタイプチェックサポートを備えたVueコンポーネントを作成する新しい方法です。

Vuexの紹介

Vuexは、Vueチームによって作成された状態管理ライブラリであり、Reduxと同じフラックスアーキテクチャに基づいています。 Vue用に特別に設計されています そしてあなたの店のよりよい組織を可能にします。 Vueアプリケーションの状態が大きくなるにつれて複雑になると、Vuexは非常に重要になります。 Vuexの最新の安定したリリースであるv4.0.0は、Vue3で導入されたCompositionAPIと、TypeScriptのより堅牢な推論をサポートしています。

フロントエンドアプリケーションの設定

フロントエンドはVue3とtypeScriptでセットアップされ、Vuexはアプリケーションの状態管理に使用されます。

まず、Vue-CLIツールを使用して、タイプスクリプトをサポートするVue3アプリを作成しましょう。

次のコマンドを使用して、Vue-CLIツールをグローバルにインストールします。

npm install --global @vue/cli

以下のコマンドを使用して、typescriptとVuexをサポートする新しいVue3アプリを作成します。

vue create vet_clinic_frontend

手動で選択する機能オプションを選択し、スペースキーを押して次のオプションを選択します。

  • Vueバージョンを選択
  • バベル
  • TypeScript
  • リンター/フォーマッター

次に、プロジェクトのバージョンとしてVue 3.x(プレビュー)を選択します。

  • クラススタイルのコンポーネント構文を使用するには、yesと入力します。
  • TypeScriptと一緒にBabelを使用するには、yesと入力します。
  • お好きなリンターを選択してください。

Vue 3アプリが正常に生成されると、Vuex(V4.x)と完全なtypescriptサポートを備えたVue3プロジェクトがセットアップされます。

typescriptを使用して、アプリケーションにタイプセーフティを追加します。開発サーバーを起動するには、ターミナルで以下のコマンドを実行し、https://localhost:8080にアクセスします。 ブラウザでプロジェクトをプレビューします。

アプリケーションのスタイル設定には、BulmaCSSフレームワークを使用します。次のコマンドを実行して、BulmaCSSをインストールします。

npm install bulma

Bulma CSSをインポートするには、次のコードをApp.vueに追加します :

//App.vue
<style lang="scss">
@import "~bulma/css/bulma.css";
</style>
アプリケーションストアの設定

アプリケーションストアはVuexでセットアップされます。ストアに変更を加えるために、アクションがコンポーネントからディスパッチされ、ミューテーションがトリガーされてストアが更新されます。

アプリケーションストアを設定するには、次の手順を実行します。

  1. 状態オブジェクトを作成します。
  2. アプリケーションで発生するミューテーションを設定します。
  3. これらの後続のミューテーションにコミットするアクションを作成します。
  4. コンポーネントのゲッターを作成して、状態データを直接計算します。
状態

状態は、すべてのコンポーネントがアクセスする必要のあるアプリケーションレベルのデータを保持するストアオブジェクトです。

state.tsを作成します 次のコードスニペットを含むストアディレクトリ内のファイル:

//src/store/state.ts
export type Prescription = {
  id: number;
  vet_prescription: string;
  prescribed_by: string;
  disease: string;
  livestock: string;
  completed: boolean;
  editing: boolean;
};
export type Data = {
  vet_prescription: string;
  prescribed_by: string;
  disease: string;
  livestock: string;
};
export type State = {
  loading: boolean;
  prescriptions: Prescription[];
  data: Data | null;
  showCreateModal: boolean;
  showEditModal: boolean;
  showPrescriptionModal: boolean;
  editModalPrescriptionId: number | undefined;
  showPrescriptionId: number | undefined;
};
export const state: State = {
  loading: false,
  prescriptions: [],
  data: null,
  showCreateModal: false,
  showEditModal: false,
  showPrescriptionModal: false,
  editModalPrescriptionId: undefined,
  showPrescriptionId: undefined,
};

ここでは、Prescriptionに型安全性を追加します およびData 。ゲッター、ミューテーション、およびアクションの定義で使用されるため、タイプもエクスポートします。最後に、Stateタイプをstateにキャストします。

ミューテーション

ミューテーションは、トリガーされたときにストアを変更するメソッドです。最初の引数として状態を受け取り、2番目の引数としてペイロードを受け取り、最終的にはペイロードでアプリケーションの状態を変更します。ミューテーションを作成するには、Vuexのドキュメントでミューテーションタイプに定数を使用することをお勧めします。

mutations.tsを作成します 次のコードスニペットを含むストアディレクトリ内のファイル:

//src/store/mutations.ts
import { MutationTree } from "vuex";
import { State, Prescription, Data } from "./state";

export enum MutationType {
  CreatePrescription = "CREATE_PRESCRIPTION",
  SetPrescriptions = "SET_PRESCRIPTIONS",
  CompletePrescription = "COMPLETE_PRESCRIPTION",
  RemovePrescription = "REMOVE_PRESCRIPTION",
  EditPrescription = "EDIT_PRESCRIPTION",
  UpdatePrescription = `UPDATE_PRESCRIPTION`,

  SetLoading = "SET_LOADING",
  SetCreateModal = "SET_CREATE_MODAL",
  SetEditModal = "SET_EDIT_MODAL",
  SetPrescriptionModal = "SET_PRESCRIPTION_MODAL",
}

上記のスニペットは、列挙型シンボルを使用して、アプリで可能なすべてのミューテーションの名前を保持しています。

次に、次のように、MutationTypeごとにコントラクト(タイプ)を宣言します。

//src/store/mutation.ts
export type Mutations = {
  [MutationType.CreatePrescription](state: State, prescription: Data): void;

  [MutationType.SetPrescriptions](state: State, prescription: Prescription[]): void;

  [MutationType.CompletePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.RemovePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.EditPrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.UpdatePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.SetLoading](state: State, value: boolean): void;

  [MutationType.SetCreateModal](state: State, value: boolean): void;

  [MutationType.SetEditModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void;

  [MutationType.SetPrescriptionModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void;
};

次に、MutationTypeごとに宣言されたコントラクトを実装します 、次のように:

//src/store/mutation.ts
export const mutations: MutationTree<State> & Mutations = {
  [MutationType.CreatePrescription](state, prescription) {
    state.data == prescription;
  },
  [MutationType.SetPrescriptions](state, prescriptions) {
    state.prescriptions = prescriptions;
  },
  [MutationType.CompletePrescription](state, newPrescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === newPrescription.id);
    if (prescription === -1) return;
    state.prescriptions[prescription] = { ...state.prescriptions[prescription], ...newPrescription };
  },
  [MutationType.RemovePrescription](state, Prescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id);
    if (prescription === -1) return;
    //If prescription exist in the state, remove it
    state.prescriptions.splice(prescription, 1);
  },
  [MutationType.EditPrescription](state, Prescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id);
    if (prescription === -1) return;
    //If prescription exist in the state, toggle the editing property
    state.prescriptions[prescription] = { ...state.prescriptions[prescription], editing: !state.prescriptions[prescription].editing };
    console.log("prescription", state.prescriptions[prescription]);
  },
  [MutationType.UpdatePrescription](state, Prescription) {
    state.prescriptions = state.prescriptions.map((prescription) => {
      if (prescription.id === Prescription.id) {
        return { ...prescription, ...Prescription };
      }
      return prescription;
    });
  },

  [MutationType.SetLoading](state, value) {
    state.loading = value;
  },
  [MutationType.SetCreateModal](state, value) {
    state.showCreateModal = value;
  },
  [MutationType.SetEditModal](state, value) {
    state.showEditModal = value.showModal;
    state.editModalPrescriptionId = value.prescriptionId;
  },
  [MutationType.SetPrescriptionModal](state, { showModal, prescriptionId }) {
    state.showPrescriptionModal = showModal;
    state.showPrescriptionId = prescriptionId;
  },
};

MutationTree Vuexパッケージに同梱されているジェネリックタイプです。上記のスニペットでこれを使用して、ミューテーションツリーのタイプを宣言しました。ミューテーションツリーとミューテーションにより、コントラクトが正しく実装されます。そうしないと、Typescriptがエラーをトリガーします。

アクション

アクションは、ミューテーションをトリガーするメソッドです。 APIへのリクエストなど、非同期タスクを処理する場合、API応答をペイロードとして使用して、対応するミューテーションを呼び出す前にアクションが使用されます。アクションを作成するときに、このシナリオの明確な実装を取得します。

アクションを作成する前に、次のように、RailsサーバーへのすべてのHttpリクエストを処理するためにAxiosをインストールします。

npm install axios --save

actions.tsを作成します 次のコードスニペットを含むストアディレクトリ内のファイル:

//src/store/actions.ts
import { ActionContext, ActionTree } from "vuex";
import { Mutations, MutationType } from "./mutations";
import { State, Prescription, Data } from "./state";
import axios from "axios";
const apiUrl = "https://localhost:3000/prescriptions";
export enum ActionTypes {
  GetPrescriptions = "GET_PRESCRIPTIONS",
  SetCreateModal = "SET_CREATE_MODAL",
  SetEditModal = "SET_EDIT_MODAL",
  RemovePrescription = "REMOVE_PRESCRIPTION",
  CreatePrescription = "CREATE_PRESCRIPTION",
  UpdatePrescription = "UPDATE_PRESCRIPTION",
}

同様に、上記のスニペットは列挙型シンボルを使用して、アプリで可能なすべてのアクションの名前を保持します。

次に、次のように、各ActionTypeのコントラクト(タイプ)を宣言します。

//src/store/actions.ts
type ActionAugments = Omit<ActionContext<State, State>, "commit"> & {
  commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>;
};

export type Actions = {
  [ActionTypes.GetPrescriptions](context: ActionAugments): void;
  [ActionTypes.SetCreateModal](context: ActionAugments): void;
  [ActionTypes.SetEditModal](context: ActionAugments): void;
  [ActionTypes.RemovePrescription](context: ActionAugments, Prescription: { id: number }): void;
  [ActionTypes.CreatePrescription](context: ActionAugments, data: Data): void;
  [ActionTypes.UpdatePrescription](context: ActionAugments, prescription: Prescription): void;
};

ActionAugments typeは、すべてのコミットを宣言されたミューテーションのみに制限し、ペイロードタイプもチェックします。

次に、ActionTypeごとに宣言されたコントラクト(タイプ)を実装します。以下のコードをaction.tsファイルに追加します:

//src/store/actions.ts
export const actions: ActionTree<State, State> & Actions = {
  async [ActionTypes.GetPrescriptions]({ commit }) {
    commit(MutationType.SetLoading, true);

    const response = await axios.get(apiUrl);

    commit(MutationType.SetLoading, false);
    commit(MutationType.SetPrescriptions, response.data);
  },

  async [ActionTypes.SetCreateModal]({ commit }) {
    commit(MutationType.SetCreateModal, true);
  },

  async [ActionTypes.SetEditModal]({ commit }) {
    commit(MutationType.SetEditModal, { showModal: true, prescriptionId: 1 });
  },

  //Optimistic update
  async [ActionTypes.RemovePrescription]({ commit }, Prescription) {
    if (Prescription != undefined) {
      commit(MutationType.RemovePrescription, Prescription);
    }

    const response = await axios.delete(`${apiUrl}/${Prescription.id}`);
  },

  async [ActionTypes.CreatePrescription]({ commit, dispatch }, Prescription) {
    const response = await axios.post(apiUrl, Prescription);
    dispatch(ActionTypes.GetPrescriptions);
  },

  async [ActionTypes.UpdatePrescription]({ commit, dispatch }, Prescription) {
    if (Prescription != undefined) {
      commit(MutationType.UpdatePrescription, Prescription);
      const response = await axios.patch(`${apiUrl}/${Prescription.id}`, Prescription);
      dispatch(ActionTypes.GetPrescriptions);
    }
  },
};

ここでは、実装されたすべてのアクションを格納するアクション変数を作成しました。同様に、ActionTree<State> & Actions コントラクトを確認します(type Actions )正しく実装されています。そうしないと、Typescriptがエラーをトリガーします。

また、GetPrescriptionsでRailsAPIエンドポイントへの非同期呼び出しを設定します アクションとトリガーされたSetPrescriptions ペイロードとして応答データを使用するミューテーションタイプ。 SetCreateModalも設定します 、SetEditModalCreatePrescriptionUpdatePrescription, およびRemovePrescription アクション。

ゲッター

ゲッターは、状態を最初のパラメーターとして受け取り、ストアの状態から計算された情報を返すメソッドです。

getters.tsを作成します 次のコードスニペットを含むストアディレクトリ内のファイル:

//src/store/getters.ts
import { GetterTree } from "vuex";
import { State, Prescription } from "./state";
export type Getters = {
  completedPrescriptionCount(state: State): number;
  totalPrescriptionCount(state: State): number;
  getPrescriptionById(state: State): (id: number) => Prescription | undefined;
};
export const getters: GetterTree<State, State> & Getters = {
  completedPrescriptionCount(state) {
    return state.prescriptions.filter((prescription) => prescription.completed).length;
  },
  totalPrescriptionCount(state) {
    return state.prescriptions.length;
  },
  getPrescriptionById: (state) => (id: number) => {
    return state.prescriptions.find((prescription) => prescription.id === id);
  },
};

上記のコードスニペットは、次のゲッターを定義しています。

  • completedPrescriptionCount –私たちの州で完了した処方箋の総数を取得する関数。
  • totalPrescriptionCount –私たちの州の処方箋の総数を取得する関数。
  • getPrescriptionById –IDで処方箋を取得する関数。

ゲッターに型安全性も追加しました。

ストア

stateを配線しましょう 、mutationsactions 、およびgetters グローバルVuexストアに。 store/index.tsを更新します 、次のように:

//src/store/index.ts
import { createStore, Store as VuexStore, CommitOptions, DispatchOptions, createLogger } from "vuex";
import { State, state } from "./state";
import { Mutations, mutations } from "./mutations";
import { Actions, actions } from "./actions";
import { Getters, getters } from "./getters";
export const store = createStore<State>({
  plugins: process.env.NODE_ENV === "development" ? [createLogger()] : [],
  state,
  mutations,
  actions,
  getters,
});
export function useStore() {
  return store as Store;
}
export type Store = Omit<VuexStore<State>, "getters" | "commit" | "dispatch"> & {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(key: K, payload: P, options?: CommitOptions): ReturnType<Mutations[K]>;
} & {
  dispatch<K extends keyof Actions>(key: K, payload?: Parameters<Actions[K]>[1], options?: DispatchOptions): ReturnType<Actions[K]>;
} & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>;
  };
};

statemutationsactions 、およびgetters ストアを含むオブジェクトをcreateStoreに渡してストアを作成する必要があります 方法。開発中、createLogger プラグインは、状態(前の状態と次の状態)とミューテーションをコンソールに記録します。アプリケーションのすべてのコンポーネントでストアにアクセスできるようにするため。アプリケーション全体に注入する必要があります。幸い、Vue-CLIツールはすでにストア全体をインポートし、アプリケーションのVueインスタンス内に渡しました。

Vuexストアをコンポーネントに統合する

このチュートリアルでは、Vue 3コンポジションAPIを使用して、フロントエンドアプリケーションのすべてのコンポーネントを作成します。

アプリコンポーネント

フロントエンドは、起動するとすぐに処方データのリストをレンダリングする必要があります。 GetPrescriptionを発送します コンポーネントのmounted()内のアクション ライフサイクルフック。コンポーネント内のストアにアクセスするには、useStore ストアを返すフックが実行されます。

//src/App.vue
<script lang="ts">
import { computed, defineComponent, onMounted } from "vue";
import PrescriptionList from "./components/PrescriptionList.vue";
import { useStore } from "./store";
import { ActionTypes } from "./store/actions";
export default defineComponent({
  components: { PrescriptionList },
  setup() {
    const store = useStore();
    const loading = computed(() => store.state.loading);
    onMounted(() => store.dispatch(ActionTypes.GetPrescriptions));
    const completedCount = computed(() => store.getters.completedPrescriptionCount);
    const totalCount = computed(() => store.getters.totalPrescriptionCount);
    return { loading, completedCount, totalCount };
  },
});
</script>
<template>
  <nav class="navbar" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="https://bulma.io">
        <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" />
      </a>
    </div>
    <div id="navbarBasicExample" class="navbar-menu">
      <div class="navbar-start">
        <a class="navbar-item"> Home </a>
        <a class="navbar-item"> About </a>
      </div>
    </div>
  </nav>
  <div class="container mx-auto mt-4">
    <h1 class="is-size-3 has-text-centered p-2 has-text-weight-bold is-success">Vet clinic Frontend</h1>
    <h3 class="has-text-centered p-2">Manage records of treated livestock in your farm</h3>
    <div v-if="loading">
      <h3 class="has-text-centered mt-4">Loading...</h3>
    </div>
    <div v-else>
      <p class="has-text-centered mt-2">{{ completedCount }} of {{ totalCount }} treated.</p>
      <PrescriptionList />
    </div>
  </div>
</template>
<style>
@import "~bulma/css/bulma.css";
</style>

ここでは、3つの計算されたプロパティを作成しました:

  • completedCountcompletedPrescriptionCountを呼び出します 完了した治療の総数を取得するgetterメソッド。
  • totalCounttotalPrescriptionCountを呼び出します 処方箋の総数を取得するgetterメソッド。
  • loading 、状態の読み込みプロパティを取得します。

Vue 3コンポジションAPIでは、テンプレートでアクセスできるように、テンプレートに必要なメソッドとプロパティを返す必要があります。 loading, completedCount, and totalCountがどのように返されたかに注目してください。 。

処方リストコンポーネント

このコンポーネントは、バックエンドから処方箋のリストを取得し、その子コンポーネントに処方箋データを渡す役割を果たします。

PrescriptionList.vueを作成します 次のコードを含むコンポーネントフォルダ内:

//src/components/PrescriptionList.vue
<template>
  <table class="table is-hoverable is-striped">
    <thead>
      <tr>
        <th><abbr title="Position">Prescription Id</abbr></th>
        <th>Treated</th>
        <th>Prescription</th>
        <th><abbr title="Won">Prescribed By</abbr></th>
        <th><abbr title="Drawn">Disease</abbr></th>
        <th><abbr title="Drawn">Livestock</abbr></th>
        <th><abbr title="Lost">Actions</abbr></th>
      </tr>
    </thead>
    <tbody v-if="prescriptions">
      <tr v-for="prescription in prescriptions" :key="prescription.id">
        <PrescriptionListItem v-bind="prescription" />
      </tr>
    </tbody>
    <tfoot>
      <CreateModal v-show="showCreateModal"></CreateModal>
      <button class="button  is-success" @click="setModal">Create Prescription</button>
    </tfoot>
  </table>
  <EditModal v-if="showEditModal" :id="editModalPrescriptionId"></EditModal>
  <Prescription v-if="showPrescriptionModal" :id="showPrescriptionId"></Prescription>
</template>
<script>
import CreateModal from "./CreateModal";
import EditModal from "./EditModal";
import Prescription from "./Prescription";
import PrescriptionListItem from "./PrescriptionListItem";
import { defineComponent, computed } from "vue";
import { useStore } from "@/store";
import { MutationType } from "@/store/mutations";
export default defineComponent({
  name: "Table",
  components: {
    CreateModal,
    PrescriptionListItem,
    Prescription,
    EditModal,
  },
  setup() {
    const store = useStore();
    const setModal = () => {
      store.commit(MutationType.SetCreateModal, true);
    };
    const showCreateModal = computed(() => store.state.showCreateModal);
    const showEditModal = computed(() => store.state.showEditModal);
    const editModalPrescriptionId = computed(() => store.state.editModalPrescriptionId);
    const showPrescriptionModal = computed(() => store.state.showPrescriptionModal);
    const showPrescriptionId = computed(() => store.state.showPrescriptionId);
    const prescriptions = computed(() => store.state.prescriptions);
    return { showCreateModal, setModal, prescriptions, showEditModal, showPrescriptionModal, editModalPrescriptionId, showPrescriptionId };
  },
});
</script>
<style scoped>
table {
  width: 100%;
}
.fa {
  font-size: 1.2rem;
  margin-left: 15px;
}
.fa:hover {
  font-size: 1.4rem;
}
</style>

setModal メソッドは、showCreateModalを設定するミューテーションを呼び出します 真の状態で、処方箋を作成するためのモーダルを起動します。

次の計算されたプロパティを作成しました:

  • showCreateModalshowCreateModalを取得します 州の財産。
  • showEditModalshowEditModalを取得します 州の財産。
  • showPrescriptionModalshowPrescriptionModalを取得します 州の財産。
  • prescription 、州から処方箋のリストを取得します。
  • showPrescriptionIdshowPrescriptiondを取得します 州の財産。
  • editPrescriptionIdeditPrescriptionIdを取得します 州の財産。
処方コンポーネント

このコンポーネントは、PrescriptionListから小道具として処方IDを受け取ります 成分。 ID小道具は、getPrescriptionByIdを介して対応するIDの処方箋を取得するために使用されます gettersメソッドを使用して、ブラウザで処方プロパティをレンダリングします。

次のコードを使用して、componentsフォルダー内にPrescription.vueを作成します。

//src/components/Prescription.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <h1>VIEW PRESCRIPTION</h1>
      <div class="card">
        <div class="card-content">
          <div class="media">
            <div class="media-content">
              <p class="title is-4">Livestock: {{ prescription.livestock }}</p>
              <p class="subtitle is-6"><b>Prescribed by:</b> {{ prescription.prescribed_by }}</p>
              <p class="subtitle is-6"><b>Disease:</b> {{ prescription.disease }}</p>
            </div>
          </div>
          <div class="content">
            <p class="subtitle is-6">Prescription: {{ prescription.vet_prescription }}</p>
          </div>
        </div>
      </div>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { computed } from "vue";
import { useStore } from "@/store";
import { MutationType } from "@/store/mutations";
export default {
  name: "PrescriptionModal",
  props: {
    id: { type: Number, required: true },
  },
  setup(props: any) {
    const store = useStore();
    const prescription = computed(() => store.getters.getPrescriptionById(Number(props.id)));
    const closeModal = () => {
      store.commit(MutationType.SetPrescriptionModal, {
        showModal: false,
        prescriptionId: undefined,
      });
    };
    return { closeModal, prescription };
  },
};
</script>
<style scoped>
h1 {
  color: #ffffff;
  text-align: center;
  font-size: 2rem;
  margin-bottom: 3rem;
}
</style>

closeModal メソッドはSetPrescriptionModalをコミットします showModalを設定するミューテーション prescriptionがfalseの状態のプロパティ 計算されたプロパティはgetPrescriptionByIdを呼び出します getter method to retrieve a prescription by its Id.

CreateModal Component

This component is responsible for creating prescriptions.

Create CreateModal.vue inside the components folder with the following code:

//src/components/CreateModal.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <form @submit.prevent="createPrescription">
        <div class="field">
          <label class="label">Prescribed By</label>
          <div class="control">
            <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" />
          </div>
        </div>
        <div class="field">
          <label class="label">Prescription</label>
          <div class="control">
            <textarea v-model="prescription" class="textarea" placeholder="Enter prescription"></textarea>
          </div>
        </div>
        <div class="field">
          <label class="label">Disease</label>
          <div class="control">
            <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" />
          </div>
        </div>
        <div class="field">
          <label class="label">Livestock</label>
          <div class="control">
            <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" />
          </div>
        </div>
        <div class="field is-grouped">
          <div class="control">
            <button type="submit" class="button is-link">Submit</button>
          </div>
          <div class="control" @click="closeModal">
            <button class="button is-link is-light">Cancel</button>
          </div>
        </div>
      </form>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
import { useStore } from "@/store";
import { Data } from "@/store/state";
import { MutationType } from "@/store/mutations";
import { ActionTypes } from "@/store/actions";
export default {
  name: "CreateModal",
  setup() {
    const state = reactive({
      prescription: "",
      prescribedBy: "",
      disease: "",
      livestock: "",
    });
    const store = useStore();
    const createPrescription = () => {
      if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return;
      const prescription: Data = {
        vet_prescription: state.prescription,
        prescribed_by: state.prescribedBy,
        disease: state.disease,
        livestock: state.livestock,
      };
      store.dispatch(ActionTypes.CreatePrescription, prescription);
      state.prescription = "";
      state.prescribedBy = "";
      state.disease = "";
      state.livestock = "";
    };
    const closeModal = () => {
      store.commit(MutationType.SetCreateModal, false);
    };
    return { closeModal, ...toRefs(state), createPrescription };
  },
};
</script>

The createPrescription method dispatches an action that makes a post request to the server, thereby creating a new prescription, while the closeModal method commits the SetPrescriptionModal mutation (which sets the showModal property in the state to false).

Working with forms and input element requires two-way data binding, and as such, we used Vue 3’s reactive method to store values used in the input fields.

Note:When using reactive , we need to use toRefs to convert the reactive object to a plain object, where each property on the resulting object is a ref pointing to the corresponding property in the original object.

EditModal Component

This component is responsible for updating prescriptions. Its logic is similar to the CreatePrescription component we discussed in the previous section.

Create EditModal.vue inside the components folder with the following code:

//src/components/EditModal.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <form @submit.prevent="updatePrescription">
        <h1>Edit Modal</h1>
        <div class="field">
          <label class="label">Prescribed By</label>
          <div class="control">
            <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" />
          </div>
        </div>
        <div class="field">
          <label class="label">Prescription</label>
          <div class="control">
            <textarea v-model="prescription" class="textarea" placeholder="Enter Prescription"></textarea>
          </div>
        </div>
        <div class="field">
          <label class="label">Disease</label>
          <div class="control">
            <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" />
          </div>
        </div>
        <div class="field">
          <label class="label">Livestock</label>
          <div class="control">
            <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" />
          </div>
        </div>
        <div class="field is-grouped">
          <div class="control">
            <button type="submit" class="button is-link">Submit</button>
          </div>
          <div class="control" @click="closeModal">
            <button class="button is-link is-light">Cancel</button>
          </div>
        </div>
      </form>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, computed, onMounted } from "vue";
import { useStore } from "@/store";
import { Prescription } from "@/store/state";
import { MutationType } from "@/store/mutations";
import { ActionTypes } from "@/store/actions";
export default {
  name: "EditModal",
  props: {
    id: { type: Number, required: true },
  },
  setup(props: any) {
    const state = reactive({
      prescription: "",
      prescribedBy: "",
      disease: "",
      livestock: "",
    });
    const store = useStore();
    const setFields = () => {
      const prescription = store.getters.getPrescriptionById(Number(props.id));
      if (prescription) {
        console.log("prescription si kolo", prescription);
        state.prescription = prescription.vet_prescription;
        state.prescribedBy = prescription.prescribed_by;
        state.disease = prescription.disease;
        state.livestock = prescription.livestock;
      }
    };
    onMounted(() => {
      setFields();
    });
    const updatePrescription = () => {
      if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return;
      const prescription: Prescription = {
        id: props.id,
        vet_prescription: state.prescription,
        prescribed_by: state.prescribedBy,
        disease: state.disease,
        livestock: state.livestock,
        completed: false,
        editing: false,
      };
      store.dispatch(ActionTypes.UpdatePrescription, prescription);
      state.prescription = "";
      state.prescribedBy = "";
      state.disease = "";
      state.livestock = "";
    };
    const closeModal = () => {
      store.commit(MutationType.SetEditModal, { showModal: false, prescriptionId: undefined });
    };
    return { closeModal, ...toRefs(state), updatePrescription };
  },
};
</script>
<style scoped>
label {
  color: #ffffff;
}
h1 {
  color: #ffffff;
  text-align: center;
  font-size: 2rem;
  margin-bottom: 3rem;
}
</style>

The createPrescription method dispatches an action that makes a put request to the server, thereby updating an existing prescription by its ID, while the closeModal method commits the SetPrescriptionModal mutation that sets the showModal property in the state to false. Calling the setFields method on the onMounted lifecycle hook triggers the getPrescriptionById getters method to fetch a prescription from the store by its ID and then updates the properties in the reactive object with the fetched prescription properties as soon as the component is mounted on the DOM.

Launching the Final Project

In the root directory of your Rails API server, run the following command to start the server:

rails server

Now, you can run the frontend application with the following command:

npm run serve

Finally, your application should be as follows:

Vue、Vuex、Railsを使用したフルスタックアプリケーションの構築

結論

We have built a CRUD API server with Rails and our frontend application on Vue 3 composition API and Vuex, all running on different servers while integrating both stacks to build a fullstack web application. I hope you have learned a great deal from this tutorial. Please reach out in the comment section below if you have any questions or suggestions. Here is the GitHub repo for the full-stack application built in this tutorial.


  1. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた

  2. Nuxt3とサーバーレスRedisの使用を開始する

    はじめに アプリケーションの使用状況を追跡したり、リソースの使用率を制限したり、キャッシュからデータをフェッチしてアプリのパフォーマンスを向上させたりする必要がある場合は、Redisがこれらの要件に対する答えであることがわかります。 Redisは、メモリ内のKey-Valueデータベースです。これはオープンソースであり、RemoteDictionaryServerの略です。 この記事では、Upstash、Redisデータベース、およびVueSSRフレームワークの最近のベータリリースであるNuxt3について説明します。これは、Redisデータベースについて説明する初心者向けの記事で、 Nux