Railsはwebpackerを使うことで簡単にVue.jsが使えるようにできます。
便利な使い方として、Vue.jsをただのページとして表示させるだけでなく、Vue.jsを使ってRailsを操作しDBからデータを取得したり、データを追加・変更・削除することもできます。
ここでは、RailsをDB操作のためのバックエンドとして、Vue.jsをWEBブラウザに表示するフロントエンドと使う方法について、実例を用いて解説しています。
Docker上にRails+Vue.jsの環境を構築する方法
Docker上にRails+Vue.jsの環境を構築する方法については、下記を参考にしてください。
(参考)Docker上にRails6を作成する手順をわかりやすく解説
なお、コンパイルを早めるために、webpack-dev-serverの利用と、dev-serverの設定変更をしておくことをお勧めします。
- Dcoker上のRailsでWebpackerのコンパイルが遅い問題を解決する方法
- webpak-dev-server時にVueファイルの変更が反映されないときの対処法
- Viewファイルの変更がページのリロードで反映されないときの対処法
テーブルとモデルの作成
マイグレーションファイルとモデル作成
DB上にテーブルを作成するために、テーブルの元となるマイグレーションファイルと、Railsを使ってテーブルを操作するためのモデルを作成します。
# rails g model <モデル名> カラム名1:型 カラム名2:型,,,,,
- gはgenerateの省略形。
rails generate model ~
でも同じ処理になります。 - モデル名は単数形で指定します。テーブル名は自動的にこの複数形になります。
- モデル名、カラム名ともに複数の単語を繋げる場合はスネークケースを使います。(アンダースコアで繋げる)
実例
Clientモデルと、clientsテーブルを作成します。
# rails g model client pj_name:string client_name:string status:integer order_date:date price:bigint memo:text
Running via Spring preloader in process 303
invoke active_record
create db/migrate/20210719082013_create_clients.rb
create app/models/client.rb
invoke test_unit
create test/models/client_test.rb
create test/fixtures/clients.yml
これにより、マイグレーションファイル db/migrate/20210719082013_create_clients.rbが生成されました。
マイグレーションファイルの中身は次のようになっています。
class CreateClients < ActiveRecord::Migration[6.1]
def change
create_table :clients do |t|
t.string :pj_name
t.string :client_name
t.integer :status
t.date :order_date
t.bigint :price
t.text :memo
t.timestamps
end
end
end
この状態でマイグレーションを実行すると、clientsテーブルを作成し、その中に、(1)pj_name, (2)client_name, (3)status, (4)order_date, (5)price, (6)memo のカラムが生成されます。
マイグレーションファイルの編集
Railsのマイグレーションファイルでは、カラム毎に、デフォルトの値や、nullを許容するかといった設定ができます。(修飾子やオプションと呼びます)
,オプション名: 値
で記述します。複数設定する場合はカンマでつなげます。
オプション | 内容 |
---|---|
default | デフォルトの値を設定する |
null | NULL値を許容するか。デフォルトはtrue(許可) |
limit | 最大サイズをバイトで指定。integer, string, text, binaryに使う |
precision | decimalの全体の桁数(※整数の桁数ではない) |
scale | decimalの小数点以下の桁数を指定。デフォルトは0 |
polymorphic | belongs_toの関連付けで使うtypeカラムを追加 |
comment | カラムにコメントつける |
(参考)Rails公式 Active Record マイグレーションのカラム修飾子
実例
class CreateClients < ActiveRecord::Migration[6.1]
def change
create_table :clients do |t|
t.string :pj_name, null: false, defualt: ""
t.string :client_name, null:false, default: ""
t.integer :status, null: false, default: 0
t.date :order_date, null: false
t.bigint :price, null:false, defualt:0
t.text :memo, null: true
t.timestamps
end
end
end
マイグレーションの実行
マイグレーションファイルが完成したら、マイグレーションを実行します。
# rails db:migrate
実例
# rails db:migrate
== 20210715105849 CreateClients: migrating ====================================
-- create_table(:clients)
-> 0.1232s
== 20210715105849 CreateClients: migrated (0.1235s) ===========================
(補足)DBを生成していない場合は、db:create後に行います。
# rails db:create db:migrate
モデルにバリデーションを追加する
Railsでテーブルにデータを格納する際のバリデーション(規則)を設定します。
バリデーションを設定すると、create, save, updateメソッドを実行するときにバリデーションの内容で確認します。
validates :<カラム名>, <ルール名>: <内容>
複数のバリデーションを指定する
カンマでつなげば、複数のバリデーションをまとめて設定することもできます。
validates :<カラム名>, <ルール名1>: <内容1>, <ルール名2>: <内容2>, <ルール名3>: <内容3>,,,
複数のカラムを同時に指定する
同じバリデーションを複数のカラムにまとめて設定することもできます。(カラム名の前の「:」を忘れずに)
validates :<カラム名1>, :<カラム名2>, :<カラム名3>, <ルール名>: <内容>
主なバリデーションルールと使い方
規則 | 実例 | 備考 |
---|---|---|
入力必須 | presence: true | |
真偽値 | inclusion: { in: [true, false] } | inclusionは含むことを指定するヘルパです |
nilを許容しない | exclusion: { in: [nil] } | exclusionは除くことを指定するヘルパです |
nilと空白を許容しない | exclusion: { in: [nil, “”] } | |
重複を許可しない(一意) | uniqueness: true | |
指定したカラムの中で一意 | uniqueness: { scope: :year } | yearカラムの中で一意 |
数値のみ | numericality: true | |
整数のみ | numericality: { only_integer: true } | |
最少値 | length: { minimum: 2 } | |
最大値 | length: { maximum: 500 } | |
最少値〜最大値の範囲 | length: { in: 6..20 } | |
長さを指定 | length: { is: 6 } | |
アルファベットのみ | format: { with: /\A[a-zA-Z]+\z/} | 正規表現で指定 |
メールアドレス | format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i} |
バリデーションのルールのことをバリデーションヘルパーと呼んだりもします。
(参考)Rails公式 Active Record バリデーション
実例
デフォルトではモデルには何も設定されていません。
class Client < ApplicationRecord
end
バリデーションを追加します。
class Client < ApplicationRecord
enum status: { "契約前": 0, "進行中": 1, "完了": 2 }
validates :pj_name, :client_name, exclusion: { in: [nil, ""] }, length: { in: 3..80 }, exclusion: { in: [nil, ""] }
validates :client_name, :client_name, exclusion: { in: [nil, ""] }, length: { in: 3..80 }, exclusion: { in: [nil, ""] }
validates :status, inclusion: { in: ["契約前", "進行中", "完了"] }, exclusion: { in: [nil, ""] }
validates :price, numericality: { only_integer: true }, exclusion: { in: [nil, ""] }
end
enum
enumとは、enumerationの略で列挙という意味です。
enum カラム名: { 選択肢 }
のように記述すると、指定した選択肢の中からプルダウンで選択できるようになります。
外部から直接データを追加する場合も想定してvalidates :status
で選択肢も限定しておくと安心です。
ActiveAdminの導入
Railsでデータの作成、更新、削除などのDB操作を行う管理画面をとても簡単に作成する方法に、ActiveAdminというgemがあります。
デザインがいけてる訳ではありませんが、操作を行うにはとても便利なライブラリです。(何より導入が簡単です)
ユーザーのログイン管理を行うdeviseというgemと一緒に導入することが多いです。ユーザー管理が必要ない場合は、ActiveAdminのみのインストールで問題ありません。
ここでは、ActiveAdminをインストールします(deviseは後からでも追加できます)
Gemfileの編集とインストール
GemfileでActiveAdminを使うことを宣言します。
gem 'activeadmin'
# bundle install
セットアップとマイグレーション
Webpackerを使う場合はオプションに--use_webpacker
をつけます。
rails g active_admin:install --skip-users --use_webpacker
必要なファイル一式を自動で生成します。
# rails g active_admin:install --skip-users --use_webpacker
Running via Spring preloader in process 491
create config/initializers/active_admin.rb
create app/admin
create app/admin/dashboard.rb
route ActiveAdmin.routes(self)
generate active_admin:webpacker
rails generate active_admin:webpacker
Running via Spring preloader in process 513
create app/javascript/packs/active_admin.js
create app/javascript/stylesheets/active_admin.scss
create app/javascript/packs/active_admin/print.scss
create config/webpack/plugins/jquery.js
insert config/webpack/environment.js
insert config/webpack/environment.js
run yarn add @activeadmin/activeadmin from "."
yarn add v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "linux" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.13: The platform "linux" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > vue-loader@15.9.2" has unmet peer dependency "css-loader@*".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
info Direct dependencies
└─ @activeadmin/activeadmin@2.9.0
info All dependencies
├─ @activeadmin/activeadmin@2.9.0
├─ jquery-ui@1.12.1
├─ jquery-ujs@1.2.3
└─ jquery@3.6.0
Done in 10.77s.
create db/migrate/20210716065845_create_active_admin_comments.rb
マイグレーションの実行
マイグレーションファイルも生成されるので、マイグレーションを行いテーブルを作成します。
rails db:migraterails db:migrate
既存モデルとActiveAdminを連携する
ActiveAdminにモデルを追加します。
rails generate active_admin:resource <モデル名>
追加したモデルの中でActiveAdminで操作を許可する項目を設定します。
app/admin/モデル名.rb のファイルを開いて、permit_paramsのコメントアウトを外し、許可したいカラム名を記述します。
permit_params :カラム名1, ;カラム名2,,,,
実例
# rails generate active_admin:resource Client
Running via Spring preloader in process 566
create app/admin/clients.rb
次に、許可するカラムを指定します。(idなど自動で設定されるカラム以外の許可したいものを記述します)
ActiveAdmin.register Client do
permit_params :pj_name, :client_name, :status, :order_date, :price, :memo
end
アプリケーションの再起動
この状態で、http://localhost:3000/admin にアクセスした時に、ActiveAdmin.routes(self) が認識されずにエラーが発生したり、No route matches [GET] “/admin” が表示される場合は、Railsを再起動すれば読み込めるようになります。
$ docker stop <Railsコンテナ名> <DBコンテナ名>
$ docker-compose up -d && docker-compose exec web bin/webpack-dev-server
以上でActiveAdminの導入と既存のモデルとの連携は完了です。
http://localhost:3000/admin にアクセスすればActiveAdminの管理画面が表示されます。
先ほど追加したClientsモデルも表示されています。
※CSSが適用されず、レイアウト崩れが発生することがありますが、今後の処理に問題はありません。
▼CSSが適用されない場合
ActiveAdminにスタイルを適用させたい場合は、splitChunksをオフにすれば適用されます。
(参考)【Rails】ActiveAdminにスタイルが適用されないときの対処法|SplitChunksを使うときの注意点
APIの作成
次にAPIを作成して、Clientモデルのデータを取得できるようにします。
後々、APIに追加処理をすることも考え、ここでは、ActionController::APIを継承した、ApiControllerを作成し、これをベースにデータにアクセスするAPIを作成します。
ApiControllerの作成
touch app/controllers/api_controller.rb
RailsでAPIの機能を使う時は、ActionController::APIを継承します。
ActionController::APIを継承した、ApiControllerを作成します。
class ApiController < ActionController::API
end
ClientsControllerの作成
clientsテーブルの情報を取得する、ClientsControllerを作成します。
複数プロジェクトを管理することを想定したディレクトリ構造にします。
apiディレクトリの下に、プロジェクト毎のディレクトリを配置し、その中に関連するモデルを操作するコントローラを作ります。
ここでは、ディレクトリをpj1とし、その中にclients_controller.rbを作成します。
- ファイルパス: app/controllers/api/pj1/clients_controller.rb
▼ファイルの作成
ビューファイルやscssファイルは不要なので、rails g controller
ではなく、直接ファイルを作成します。
mkdir -p app/controllers/api/pj1
touch app/controllers/api/pj1/clients_controller.rb
▼ファイルの中身
class Api::Pj1::ClientsController < ApiController
before_action :set_client, only: [:show]
#例外処理
rescue_from Exception, with: :render_status_500
rescue_from ActiveRecord::RecordNotFound, with: :render_status_404
#一覧
def index
@clients = Client.all
render json: @clients
end
#詳細
def show
render json: @client
end
private
def set_client
@client = Client.find(params[:id])
end
def render_status_404(exception)
render json: { errors: [exception] }, status: 404
end
def render_status_500(exception)
render json: { errors: [exception] }, status: 500
end
end
before_action
before_actionとは、アクションを実行する前に行うメソッドを指定できる機能です。
before_action :メソッド名
ここでは、メソッドとして、個々のクライアントのデータを取得するset_clientメソッドを指定しています。
onlyオプションを指定すると、メソッドを実行するアクションを絞りこむことができます。
only: [:show]
なので、showアクションを実行する前のみset_client
メソッドを実行します。
Api::pj1::ClientsController < ApiController
Api::pj1::ClientsControllerは、Apiという名前空間の中の、pj1という名前空間の中のClientsControllerクラスという意味です。
一般的に名前空間はディレクトリ構造に合わせます。
クラス名 < ApiController の <は継承を意味します。指定したクラスが、ApiControllerクラスの内容を引き継ぎます。
rescue_from
rescue_fromは例外処理を行うRailsの機能です。例外とはエラーのことで、例外処理とはエラーが発生したときに実行する処理のことです。
rescue_from 例外名, with: :メソッド名
キャッチする例外と、例外をキャッチしたときに実行するメソッドを指定します。メソッドは同じコントローラファイルの中にprivateメソッドとして記述するのが一般的です。
rescue_fromは下から順に適用されるので、上位クラスの例外処理は上に記述する必要があります。
ここでは、DB上の存在しない行(レコード)のデータを取得しようとしたときに発生するエラーActiveRecord::RecordNotFoundと、例外クラスの祖先(すべての例外)であるExceptionを指定しています。
実行する処理はprivateメソッドに記述しています。
private
(省略)
def render_status_404(exception)
render json: { errors: [exception] }, status: 404
end
def render_status_500(exception)
render json: { errors: [exception] }, status: 500
end
エラーメッセージをexceptionという変数に代入し、ステータスコードを指定(404と500)して、エラーメッセージをJSON形式で出力します。
ステータスコードを指定せずにステータス200の状態でエラーページを表示させるとソフト404というSEO的にNGな処理になってしまいます。
render json: @clients
renderはブラウザに返すレスポンスを指定します。jsonオプションを使うと指定した内容をJSON形式でブラウザに表示します。
@clientsはClient.all
なので、clientsテーブルのすべてのデータを表示する処理になります。
ルーティングの設定
Clientsコントローラが作成できたので、対応するルーティングを作成します。
コントローラの名前空間はApi::pj1::なので、ルーティングも同じ構造になるようにnamespaceを指定します。
Rails.application.routes.draw do
ActiveAdmin.routes(self)
(省略)
# APIコントローラへのルーティング
namespace :api, {format: 'json'} do
namespace :pj1 do
resources :clients, only: [:index, :show]
end
end
end
この処理で、次のルーティングが追加されます。
# rails routes | grep api
api_pj1_clients GET /api/pj1/clients(.:format)
api/pj1/clients#index {:format=>/json/}
api_pj1_client GET /api/pj1/clients/:id(.:format)
api/pj1/clients#show {:format=>/json/}
resourcesとは?
resourcesとは、DB操作に必要なルーティング一式をまとめて生成してくれる便利な指定です。
reousercesの後にコントローラー名を指定します。
resources :コントローラ名
上記のように指定すると、次の8つのルーティングが生成されます。
アクション | HTTP動詞 | パス | 内容 |
---|---|---|---|
index | GET | /コントローラ名 | 一覧を表示 |
create | POST | /コントローラ名 | データ追加処理 |
new | GET | /コントローラ名/new | データ追加用の画面 |
edit | GET | /コントローラ名/:id/edit | 指定したデータを変更する画面 |
show | GET | /コントローラ名/:id | 指定したデータの詳細画面 |
update | PATCH | /コントローラ名/:id | 指定したデータの更新処理(変更箇所のみ) |
update | PUT | /コントローラ名/:id | 指定したデータの更新処理(全体を更新) |
destroy | DELETE | /コントローラ名/:id | 指定したデータの削除処理 |
onlyオプションを使ってアクションを指定すれば、指定したアクションへのルーティングのみを生成します。
namespaceとは?
namespaceとは、Railsのルーティングで名前空間を定義してグループ化するための処理です。基本的にresourcesとセットで使います。
namespace :<名前空間> do
resources :<コントローラ名>
end
APIの動作確認(ブラウザに表示)
APIの動作確認のため、clientsテーブルのすべてのデータがブラウザ上に正しく表示されるか確認します。
まずは、ActiveAdminの管理画面経由でclientsテーブルにデータを追加します。(追加するデータはお好みで)
http://localhost:3000/admin/clients の中の、NewClientから追加処理を行います。
直接アクセスする場合は、http://localhost:3000/admin/clients/new です。
▼データ追加後の例
例として2つデータを追加しました。
追加したデータをAPIコントローラ経由でブラウザ上に表示します。
先ほどのルーティングで追加したURIにアクセスします。
clients一覧ページへのアクセス例
http://localhost:3000/api/pj1/clients
client詳細ページへのアクセス例
http://localhost:3000/api/pj1/clients/7
どちらも正しくJSON形式でデータが表示されていることを確認できました。以上でAPIの設定はOKです。
Vue.jsの導入
次にフロントとしてブラウザにWEBページを表示するためのVue.jsをRailsアプリケーションに追加します。
WebpackerでVueをインストールする
RailsでVue.jsを読み込むために、Webpackerを使ってRailsにVueを追加します。これを追加することで、Vueファイルのコンパイルが可能になります。
また、デフォルトで、hello_vue.jsという「Hello Vue!」をブラウザに表示するテスト用のファイルも生成してくれます。
rails webpacker:install:vue
実例
Dockerコンテナの外から実行する場合は、サービスを指定して、引数でコマンドを渡します。
$ docker-compose run web rails webpacker:install:vue
省略
Webpacker now supports Vue.js 🎉
webpack関連のファイルが更新され、app.vueやhello_vue.jsといったファイルが生成されます。
modified: config/webpack/environment.js
modified: config/webpacker.yml
modified: package.json
modified: yarn.lock
Untracked files:
(use "git add <file>..." to include in what will be committed)
app/javascript/app.vue
app/javascript/packs/hello_vue.js
config/webpack/loaders/
vue-loaderのバージョンを下げる(コンパイルエラー対応)
rails webpacker:install:vue
でVueをインストールした状態では、コンパイルエラーが発生します。
これは、vue-loaderのバージョンが新しすぎて、必要なライブラリが入っていないためです。このため、vue-loaderのバージョンを15系まで落とします。
package.jsonファイルの修正
Webpackerはgemではなくyarn(node.js)で管理されているので、package.jsonに記載されている vue-loaderのバージョンを修正します。
"dependencies": {
(省略)
"vue-loader": "15.9.2",
(省略)
},
インストール
yarnを使って、package.jsonの内容を読み込み必要なライブラリをインストールします。
# yarn install
▼Dockerコンテナの外から実行する場合
docker-compose.ymlファイルのあるディレクトリで以下を実行します。
# docker-compose run <Railsのサービス名> yarn install
実例
$ docker exec -it rails-vue-web bash
root@59229383d91c:/rails-vue# yarn install
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "linux" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.13: The platform "linux" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > vue-loader@15.9.2" has unmet peer dependency "css-loader@*".
[4/4] Building fresh packages...
success Saved lockfile.
Done in 21.29s.
vue-loader@15.9.2″のインストールが完了しました。
エラー内容の詳細についてはこちらをご参考。
Babelの設定を変更する(大量の警告を非表示にする)
デフォルトの状態では、コンパイル時にBabel関連の警告が大量に表示されるため、この表示をオフにします。
(エラーではなく警告なのでコンパイルは実行できるのですが、コンパイルのたびにターミナル(黒画面)が警告で埋まります)
["@babel/plugin-proposal-private-methods", { "loose": true }]
をルート直下にあるbabel.config.jsファイルに追記します。
plugins: [
(省略),
["@babel/plugin-proposal-private-methods", { "loose": true }]
].filter(Boolean)
保存すれば完了です。
次回のコンパイル以降は警告が表示されなくなります。
実際の記述例
module.exports = function(api) {
var validEnv = ['development', 'test', 'production']
var currentEnv = api.env()
var isDevelopmentEnv = api.env('development')
var isProductionEnv = api.env('production')
var isTestEnv = api.env('test')
if (!validEnv.includes(currentEnv)) {
throw new Error(
'Please specify a valid `NODE_ENV` or ' +
'`BABEL_ENV` environment variables. Valid values are "development", ' +
'"test", and "production". Instead, received: ' +
JSON.stringify(currentEnv) +
'.'
)
}
return {
presets: [
isTestEnv && [
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
],
(isProductionEnv || isDevelopmentEnv) && [
'@babel/preset-env',
{
forceAllTransforms: true,
useBuiltIns: 'entry',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol']
}
]
].filter(Boolean),
plugins: [
'babel-plugin-macros',
'@babel/plugin-syntax-dynamic-import',
isTestEnv && 'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-class-properties',
{
loose: true
}
],
[
'@babel/plugin-proposal-object-rest-spread',
{
useBuiltIns: true
}
],
[
'@babel/plugin-transform-runtime',
{
helpers: false
}
],
[
'@babel/plugin-transform-regenerator',
{
async: false
}
],
["@babel/plugin-proposal-private-methods", { "loose": true }]
].filter(Boolean)
}
}
エラーの詳細やコンパイル結果についてはこちらをご参照ください。
hello_vue.jsをmain.jsに変更する
Webpackのコンパイル対象となる大元のファイル名をhello_vue.jsからmain.jsに変更します。(変更しなくても使えますが、変更するとより本格的に見えるようになります)
ファイル名の変更
hello_vue.js を main.js に変更します。
$ mv app/javascript/packs/hello_vue.js app/javascript/packs/main.js
home/index.html.erbの修正
app/views/home/index.html.erb にWebpackerを使ってコンパイル後の hello_vue.vue を読み込んでいる記述を変更します。
<%= javascript_pack_tag 'main' %>
<%= stylesheet_pack_tag 'main' %>
manifest.jsonの修正
manifest.jsonはコンパイル時にWebpackerが自動で生成するファイルです。元のファイルとコンパイル後のファイルの対応表になっています。
hello_vue.jsの状態で既にコンパイルされているため、この記述を変更します。
hello_vue を main に一括置換します。
{
"application.js": "/packs/js/application-e421b4aa3f716bebdab1.js",
"application.js.map": "/packs/js/application-e421b4aa3f716bebdab1.js.map",
"entrypoints": {
"application": {
"js": [
"/packs/js/application-e421b4aa3f716bebdab1.js"
],
"js.map": [
"/packs/js/application-e421b4aa3f716bebdab1.js.map"
]
},
"main": {
"js": [
"/packs/js/main-f10dedba5d1ccdc85afe.js"
],
"js.map": [
"/packs/js/main-f10dedba5d1ccdc85afe.js.map"
]
}
},
"main.js": "/packs/js/main-f10dedba5d1ccdc85afe.js",
"main.js.map": "/packs/js/main-f10dedba5d1ccdc85afe.js.map",
}
(参考)修正前のファイル
{
"application.js": "/packs/js/application-e421b4aa3f716bebdab1.js",
"application.js.map": "/packs/js/application-e421b4aa3f716bebdab1.js.map",
"entrypoints": {
"application": {
"js": [
"/packs/js/application-e421b4aa3f716bebdab1.js"
],
"js.map": [
"/packs/js/application-e421b4aa3f716bebdab1.js.map"
]
},
"hello_vue": {
"js": [
"/packs/js/hello_vue-f10dedba5d1ccdc85afe.js"
],
"js.map": [
"/packs/js/hello_vue-f10dedba5d1ccdc85afe.js.map"
]
}
},
"hello_vue.js": "/packs/js/hello_vue-f10dedba5d1ccdc85afe.js",
"hello_vue.js.map": "/packs/js/hello_vue-f10dedba5d1ccdc85afe.js.map",
}
以上でmain.jsへの置き換えは完了です。保存して、ブラウザをリロードしページが表示されればOKです。
ビューファイルを編集してVue.jsの内容を表示する
ここまでで、Vue.jsファイルをコンパイルする準備が整ったので、実際に、ブラウザにVue.jsの内容を出力します。
出力するファイルは、rails webpacker:install:vue
で自動生成されたテスト用の、app.vueを使います。
表示するルーティングには作成済みのHomeコントローラーのindexアクションを使います。app/views/home/index.html.erb を以下のように書き換えます。
<%= javascript_pack_tag 'hello_vue' %>
<%= stylesheet_pack_tag 'hello_vue' %>
保存して、Railsを起動するとコンパイルが走ります。
http://localhost:3000/home/index にアクセスして「Hello Vue!」と表示されればvue.jsファイルの表示に成功です。
<%= javascript_pack_tag ‘hello_vue’ %>とは?
javascript_pack_tagはWebpackerでコンパイルしたjsファイルを読み込むためのヘルパーメソッドです。
Webpackerは app/javascript/packs 配下にあるJavaScriptファイルを、コンパイルして public/packs/js 配下に出力します。そのコンパイルしたファイルを読み込んでいます。
javascript_pack_tag '元のファイル名'
として使います。元のファイル名は .js を省略して指定します。
<%= stylesheet_pack_tag ‘hello_vue’ %>とは?
stylesheet_pack_tagはWebpackerでコンパイルしたcssファイルを読み込むためのヘルパーメソッドです。
Webpackerは app/assets/stylesheets 配下にあるscssやcssファイルを、コンパイルして public/assets 配下に出力します。そのコンパイルしたファイルを読み込んでいます。
stylesheet_pack_tag '元のファイル名'
として使います。元のファイル名は拡張子を省略して指定します。
<%= %>とは?
<%= 処理 %>
は、erbの中で使うHTMLタグと一緒に使う記述で、記載した処理結果を出力(print)するためのものです。
<% 処理 %>
とした場合は、処理は行いますが、その結果を出力しません。
なお、<%== 処理 %>
のようにイコールを2つにすると、中に記述したタグなどをエスケープせずそのまま表示します。<%# %>
はコメントアウトに使います。
Vue.jsにAPIの内容を表示する
モデルからデータを取得するAPIと、フロント側にページを表示するVue.jsの用意が完了したので、Vue.js経由でAPIにアクセスして、テーブルのデータを表示させます。
axiosのインストール
APIからデータを取得するために、axiosというモジュールを使います。
axiosとは、Node.jsで作成されたモジュールで、非同期にHTTP通信を行うときに使います。インストールはnpm、yarn、CDNリンクのいずれでも可能です。
#npmの場合
npm install axios
#yarnの場合
yarn add axios
#CDNの場合(headerにリンクを追加)
<script src="https://unpkg.com/axios/dist/axios.min.js"><script>
実例
ここでは、yarnを使ってインストールします。
#yarn add axios
yarn add v1.22.5
[1/4] Resolving packages...
・
・
・
info All dependencies
└─ axios@0.21.1
Done in 177.29s.
Doneが表示されれば完了です。
axiosでAPIのデータを取得する
いきなり表示部を成型すると何が行われているかわかりにくいので、まずはaxiosを使ってAPI経由で取得したデータをブラウザに表示します。
app.vueの内容を次のように変更します。
<template>
<div id="app">
{{ clients }}
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
clients: []
}
},
mounted(){
axios
.get('/api/pj1/clients')
.then(response => (this.clients = response.data))
}
}
</script>
<style scoped>
</style>
templateタグ
.vueファイルにおけるtemplateタグの中身はブラウザ上に表示するHTML(vueの記述が使えます)になります。
<template>
<div id="app">
{{ clients }}
</div>
</template>
{{ }}
はマスタッシュ構文といい、この中で変数名やメソッド名を記述すると、その内容を呼び出すことができます。
{{ clients }}
とすることで、dataで定義したclients変数の中身を表示します。
import axios from ‘axios’;
スクリプトタグの直下で、axiosモジュールをaxiosという名前で読み込んでいます。
▼import fromの基本構文
import エクスポート名 from モジュール
export default
exportとは、JavaScriptをモジュール化して外から呼び出せるようにする記述です。
exportには名前付きエクスポートと、デフォルト(default)エクスポートがあります。
通常のexportは変数や関数毎に名前をつけて呼び出します(名前付きエクスポート)。これに対して、export defaultとすると、その処理の中身全体を1つのモジュールとして出力することができます。
vue単一コンポーネントの場合、このexport defaultにmountedやmethod、dataなどvueの処理を記述します。
data
dataオプションでは、変数を定義します。
data(){
return{
clients: []
}
},
clientsという変数を、デフォルトが空の配列として定義しています。
mounted
mountedはビューインスタンスを生成するときに実行されます。ブラウザでビュー全体がレンダリングされる前に読み込まれます。
mountedの実行タイミングはmain.js(デフォルトではhello_vue.js)に記述があります。
const app = new Vue({
vuetify, //追加
render: h => h(App)
}).$mount()
document.body.appendChild(app.$el)
Vueで各処理が実行されるタイミングについてはVueのライフサイクルをご参考ください。
axios
mountedの中のaxiosで、importしたaxiosを使っています。この処理がAPIからデータを取得する処理です。
axios
.メソッド('HTTPアクセスするパス')
.then(変数名 => (処理))
上記は改行していますが、メソッドチェンでつながった処理になります。
axios.メソッドで指定したURLにHTTP動詞でアクセスして、ページの内容を取得します。.thenで受け取ったデータを変数に格納して、処理の中で使えるようにしています。
axios
.get('/api/pj1/clients')
.then(response => (this.clients = response.data))
ここでは、GETメソッドで相対パス(/api/pj1/clients)を指定しています。
そして取得したページの内容をresponseという変数に代入して、responseの中のdataプロパティの値を、Vue.jsで定義した変数clietnsに代入しています。
ブラウザ上での表示
app.vueを開く指定をしているパスにアクセスして、APIの処理結果がVueファイルで正しく表示できるか確認します。
ActiveAdmin経由で登録したデータをVueファイル上に表示することができました。
ルーティングの設定
Vue.jsを使ってSPAでページを遷移するためにvue-routerというモジュールを使用します。
vue-routerを使って、/clientsにアクセスした場合に一覧ページを開き、/clients/:idにアクセスしたときに詳細ページを開くようにします。
テンプレートのディレクトリ構造
一覧ページと詳細ページのテンプレートは、componentsの中にclientsディレクトリを作成し、Clietns.vueとClient.vueを作成します。
また、トップページとして、Top.vueを作成します。
ディレクトリ構造は以下のようになります。
|- javascript
| |
| |- packs
| | |- main.js
| |
| |- app.vue
| |
| |- components
| |- Top.vue
| |
| |- clients
| |- Clients.vue
| |- Client.vue
以下の手順でルーティングを設定したときにファイルが存在しないとコンパイルエラーになるので、先にファイルだけ生成しておきます(中身は後ほど作成します)
vue-routerのインストール
まずは、vue-routerをインストールします。npmかyarnを使ってインストールします。
▼yarn
yarn add vue-router
▼npm
npm install vue-router
ルーティングの設定
vue-routerを使ったルーティングの設定は、app.vueファイルに記述します。
ここでは大きく、vue-routerの設定と、ルーティングの設定の2つを行っています。
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
import Vue from 'vue'
import VueRouter from 'vue-router'
import Top from './components/Top'
import Client from './components/clients/Client'
import Clients from './components/clients/Clients'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Top,
name: 'top',
},
{
path: '/clients',
component: Clients,
name: 'clients',
},
{
path: '/clients/:id(\\d+)',
component: Client,
name: 'client',
}
]
})
export default {
router
}
</script>
<style lang="scss" scoped>
</style>
templateタグの中に記述した<router-view></router-view>
で、ルーティングで設定したVueテンプレートを読み込みます。
ルーティングの設定はVueRouterインスタンスの中の、routesオプションの記述になります。
const router = new VueRouter({
routes: [
{
path: '/',
component: Top,
name: 'top',
},
{
path: '/clients',
component: Clients,
name: 'clients',
},
{
path: '/clients/:id(\\d+)',
component: Client,
name: 'client',
}
]
})
pathでURIを指定し、router-viewタグの中で呼び出すテンプレートをcomponentで指定します。nameを使えば、このルーティングに名前をつけることができます。
トップページの作成
トップページのテンプレートになる、Top.vueは以下のようにします。
<template>
<div>
<ul>
<router-link to="/clients" tag="li">クライアント一覧</router-link>
</ul>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
router-link
タグはvue-routerの機能の一つで、指定したルーティングへのリンクを生成します。
通常はコンパイル後にaタグに変換されますが、tagオプションを使うと指定したタグとしてコンパイルすることができます(もちろんリンクも機能します)
▼ブラウザの表示
ブラウザに表示される内容は次のようになります。
表示されている、「クライアント一覧」をクリックすると/clientsページに飛びます。
vue-routerを使ったSPAでは、ルートディレクトリに/#/が入ります(#をハッシュと呼びます)。モードをhistoryに変更すれば、このハッシュを外すこともできます。
一覧画面の作成
/clientsのルーティングで表示する一覧画面のテンプレートClietns.vueを作成します。
すべてのデータを表示するのは詳細画面の役割として、一覧画面では、client_name, pj_name, statusの3つのみを表示するようにします。
Clients.vueテンプレートの編集
Clients.vueの中身は以下のようにします。
<template>
<div>
<table>
<tbody>
<tr>
<th>Project Name</th>
<th>Client Nmae</th>
<th>Status</th>
</tr>
<tr v-for="(client, index) in clients" :key="index">
<td>{{ client.client_name }}</td>
<td>{{ client.pj_name }}</td>
<td>{{ client.status }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
clients: []
}
},
mounted(){
axios
.get('/api/pj1/clients')
.then(response => (this.clients = response.data))
}
}
</script>
<style scoped>
</style>
Vue.jsではv-forを使えばループさせることができます。
v-forの基本構文は次のようになります。
<開始タグ v-for="(変数名, インデックス用変数名) in ループさせるデータ" :key="固有となる変数">
インデックス番号も抜き出したいときは、第2引数でインデックス番号を格納する変数名を指定してます。
また、同じタグの中で:keyでループごとに固有となる値を指定します。これが無くても使うことはできるのですが、エディタやconsoleにエラーが出ます。
:keyの指定はインデックス番号以外にも、ループさせるデータの中の固有のidなども使えます。
<開始タグ v-for="変数名 in ループさせるデータ" :key="変数名.id">
▼ブラウザの表示
ブラウザに表示される内容は次のようになります。
コントローラの編集
一覧画面で使うデータはclient_name, pj_name, statusの3つのみなので、DBからデータを取得してくるclientsコントローラのindexアクションで取得してくる情報もこの3つに絞ります。
モデルに対してselectメソッドを使用します。
def index
@clients = Client.select(:client_name, :pj_name, :status, :id)
render json: @clients
end
詳細画面の作成
続いて詳細画面を作成します。
大枠の構成は一覧ページ(Clients.vue)と同じですが、変更点は3か所です。
- templateの中身(ブラウザに表示する内容)
- dataの変数名(単数形にする)
- axiosのアクセス先URL(:idが入ったものになる)
<template>
<div>
<dl>
<dt>client_id</dt>
<dd>{{ client.id }}</dd>
<dt>client_name</dt>
<dd>{{ client.client_name }}</dd>
<dt>pj_name</dt>
<dd>{{ client.pj_name }}</dd>
<dt>status</dt>
<dd>{{ client.status }}</dd>
<dt>price</dt>
<dd>{{ client.price }}</dd>
<dt>order_date</dt>
<dd>{{ client.order_date }}</dd>
<dt>memo</dt>
<dd>{{ client.memo }}</dd>
</dl>
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
client: []
}
},
mounted(){
axios
.get(`/api/pj1/clients/${this.$route.params.id}.json`)
.then(response => (this.client = response.data))
}
}
</script>
<style lang="scss" scoped>
dl {
display: flex;
flex-wrap: wrap;
padding: 10px;
dt {
width: 30%;
border: 1px solid gray;
padding: 10px;
}
dd {
width: 50%;
border: 1px solid gray;
padding: 10px;
}
}
</style>
axiosの設定内容
axiosの設定が以下のようになっています。
axios
.get(`/api/pj1/clients/${this.$route.params.id}.json`)
.then(response => (this.client = response.data))
this.$route.params.id
$route
というのはvue-routerにおいて現在のルートの情報が入ったオブジェクトです。この中のparamsプロパティには、URIで指定したパラメータが格納されています。
ここでは、/:id
という情報が欲しいので、 $route.params.id
として情報を取得しています。(export defaultの中なので、this.をつけるのを忘れずに)
バッククオートと${}
axiosのURLの指定を、`
(バッククオート)で囲んで、中で変数を${変数名}
として使っています。
これはテンプレートリテラルというJavaScriptの書式で、文字列と変数を結合することができます。
ブラウザの表示
ブラウザでclients/:id にアクセスしたときの表示は以下のようになります。
一覧ページに詳細ページへのリンクを設置
一覧ページの各データ(tableタグ)の右端に詳細ページへのリンクを設置します。
router-linkタグを使って、詳細ページをルート名前で指定し、パラメータを渡します。
<router-link :to="{ name: 'client', params: { id: client.id }}">
{{ client.id }}
</router-link>
ルート名はapp.vueのルーティングのnameオプションで指定した値になります。(ここでは、clientです)
パラメータを渡すには、paramsというプロパティの中で、キーと値を指定します。
Clients.vue全体のコードを以下のようにします。(ついでにスタイルも調整)
<template>
<div>
<table>
<tbody>
<tr>
<th>Project Name</th>
<th>Client Nmae</th>
<th>Status</th>
<th>Details Link</th>
</tr>
<tr v-for="(client, index) in clients" :key="index">
<td>{{ client.client_name }}</td>
<td>{{ client.pj_name }}</td>
<td>{{ client.status }}</td>
<td><router-link :to="{ name: 'client', params: { id: client.id }}">{{ client.id }}</router-link></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data(){
return{
clients: []
}
},
mounted(){
axios
.get('/api/pj1/clients')
.then(response => (this.clients = response.data))
}
}
</script>
<style lang="scss" scoped>
table{
text-align: center;
th{
padding: 10px;
color: #FF9800;
background: #fff5e5;
}
td{
padding: 10px;
border: 1px solid lightgray;
}
}
</style>
ブラウザで/clientsにアクセスすると、表示は次のようになります。
右端のリンクをクリックすると、詳細ページに移動することができます。
新規登録ページの作成
ここまでで、Railsのモデル経由でDBから取得したデータをVue.jsで表示することができました。
続いて、Vue.js経由でDBに新たにデータを登録できるようにします。
新規登録ページ作成の流れ
Rails側にテーブルへの新規登録機能を追加し、Vue.js経由でPOSTメソッドでそのURLにアクセスするようにします。
- Railsのresourcesにcreateのルーティングを追加
- Railsのコントローラにcreateアクションを追加
- 新規登録用のVueテンプレートを作成
- Vueに新規登録用のルーティングを追加
Railsのresourcesにcreateのルーティングを追加
まずは、バックエンドとなるRailsにcreateアクション用のルーティングを追加します。
routes.rbのresourcesのonlyオプションに:create
を追記します。
Rails.application.routes.draw do
ActiveAdmin.routes(self)
(省略)
# APIコントローラへのルーティング
namespace :api, {format: 'json'} do
namespace :pj1 do
resources :clients, only: [:index, :show, :create]
end
end
end
以下のようなルーティングが追加されます。
#rails routes
Prefix Verb URI Pattern Controller#Action
api_pj1_clients POST /api/pj1/clients(.:format) api/pj1/clients#create {:format=>/json/}
Railsのコントローラにcreateアクションを追加
createアクションとメソッドの追記
clientsコントローラにcreateアクションを追加します
#新規登録
def create
@client = Client.new(client_params)
if @client.save
render json: @client, status: :created
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
clientモデルの.newメソッドでインスタンスを生成します。引数に、client_paramsを指定しています。
ストロングパラメータの設定
これは、セキュリティ強化のためストロングパラメータ用のメソッドです。コントローラの下部にclient_paramsの処理も追記します。
private
(省略)
#ストロングパラメータの設定
def client_params
params.fetch(:client, {}).permit(:client_name, :pj_name, :status, :order_date, :price, :memo)
end
status
renderで指定した変数をJSON形式で表示します。その時にステータスをシンボルで指定しています。
シンボル | ステータスコード | 内容 |
---|---|---|
:created | 201 | リクエストが成功し、リソースの作成が完了 |
:unprocessable_entity | 422 | リクエストは正しいが、中の処理が不適切 |
clientsコントローラの全貌
clientsコントローラのコードは以下のようになります。
class Api::Pj1::ClientsController < ApiController
before_action :set_client, only: [:show]
#例外処理
rescue_from Exception, with: :render_status_500
rescue_from ActiveRecord::RecordNotFound, with: :render_status_404
#一覧
def index
# @clients = Client.all
@clients = Client.select(:client_name, :pj_name, :status, :id)
render json: @clients
end
#詳細
def show
render json: @client
end
#新規登録
def create
@client = Client.new(client_params)
if @client.save
render json: @client, status: :created
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
private
def set_client
@client = Client.find(params[:id])
end
#ストロングパラメータの設定
def client_params
params.fetch(:client, {}).permit(:client_name, :pj_name, :status, :order_date, :price, :memo)
end
def render_status_404(exception)
render json: { errors: [exception] }, status: 404
end
def render_status_500(exception)
render json: { errors: [exception] }, status: 500
end
end
新規登録用のVueテンプレートを作成
フロントエンド(Vue.js)側で、新規登録用のVueテンプレートを作成します。
componentsのclients配下にClientNew.vueを作成します。
|- javascript
| |
| |- packs
| | |- main.js
| |
| |- app.vue
| |
| |- components
| |- Top.vue
| |
| |- clients
| |- Clients.vue
| |- Client.vue
| |- ClientNew.vue
ClientNew.vueの作成
新規登録用のテンプレートとなるClientNew.vueの中身は以下のようにします。
<template>
<form @submit.prevent="createClient">
<div class="error-wrapper" v-if="errors.length != 0">
<ul v-for="error in errors" :key="error">
<li><font color="red">{{ error }}</font></li>
</ul>
</div>
<div>
<label>client_name</label>
<input v-model="client.client_name" type="text">
</div>
<div>
<label>pj_name</label>
<input v-model="client.pj_name" type="text">
</div>
<div>
<label>status</label>
<select v-model="client.status">
<option>契約前</option>
<option>進行中</option>
<option>完了</option>
</select>
</div>
<div>
<label>order_date</label>
<input v-model="client.order_date" type="date">
</div>
<div>
<label>price</label>
<input v-model="client.price" type="number" min="0">
</div>
<div>
<label>memo</label>
<textarea v-model="client.memo"></textarea>
</div>
<button type="submit">送信</button>
</form>
</template>
<script>
import axios from 'axios';
export default {
data(){
return {
client: {
client_name: '',
pj_name: '',
status: '',
order_date: '',
price: '',
memo: '',
},
errors: ''
}
},
methods: {
createClient(){
console.log(this.client)
axios
.post('/api/pj1/clients', this.client)
.then(response => {
let res = response.data;
this.$router.push({ name: 'client', params: { id: res.id } });
})
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
});
}
}
}
</script>
<style lang="scss" scoped>
div:not(.error-wrapper){
display: flex;
}
label{
width: 15%;
}
input, select, textarea{
margin: 3px;
width: 30%;
border: 1px solid gray;
}
button{
margin: 10px;
display: inline-block;
padding: 0.5em 1em;
text-decoration: none;
background: #668ad8;
color: #FFF;
border-bottom: solid 4px #627295;
border-radius: 3px;
&:active{
-webkit-transform: translateY(4px);
transform: translateY(4px);
border-bottom: none;
}
}
</style>
@submit.prevent=”createClient”
@submit.prevent
とは、送信ボタンをクリックしたときに、actionで指定したページへの遷移を行わない(prevent)する指定です。
@submit.prevent="メソッド名"
とすることで、送信ボタンがクリックされたときに指定したメソッドを実行することができます。
ここでは、送信ボタンをクリックした後に、createClientメソッドを実行します。その中で、axiosを使ってPOSTメソッドでAPI側の新規登録用のURLにアクセスします。
エラー処理
冒頭のulタグとliタグではエラーが存在する場合に、エラーを表示する処理になります。
<div class="error-wrapper" v-if="errors.length != 0">
<ul v-for="error in errors" :key="error">
<li><font color="red">{{ error }}</font></li>
</ul>
</div>
v-if="errors.length != 0
でエラーが存在する場合に、v-forでエラーを一つづつ表示します。
エラーの情報が入った変数errorsは、export defaultのdataオプションで定義しています。
エラー情報は、axiosのcatchの処理の中で定義しています。
axios
.post('/api/pj1/clients', this.client)
(省略)
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
catchはaxiosでエラーが発生したときに行う処理です。引数で指定したerrorに取得した情報が入ります(名前はなんでもいいです)
エラー情報が存在する場合に、this.errors = error.response.data.errors
で、errors変数に取得したエラーメッセージを代入しています。
フォームの項目(v-model)
フォームの各項目の基本形は次のようになっています。
<div>
<label>表示するラベル名</label>
<input v-model="変数" type="タイプ">
</div>
v-modelを使うことで、dataプロパティで定義している変数と連動させることができます。(双方向バインディングといいます)
axiosのPOSTメソッド
送信ボタンをクリックすると、createClientイベントを実行し、axiosを使ってメソッドでAPIにアクセスします。
createClient(){
console.log(this.client)
axios
.post('/api/pj1/clients', this.client)
.then(response => {
let res = response.data;
this.$router.push({ name: 'client', params: { id: res.id } });
})
axios.post('URL', オブジェクト)
とすることで、指定したURLにメソッドでアクセスし、第2引数のオブジェクトを渡します。
渡すオブジェクトは、dataで指定しているclient変数です。デフォルトの中身は空ですが、v-modelでinputタグに入力した値が入ります。
client: {
client_name: '',
pj_name: '',
status: '',
order_date: '',
price: '',
memo: '',
},
this.$router.push
axiosのthen(通信成功時)の処理の一番最後に、this.$router.push
があります。
これは、指定したVueのルーティングにリンクする処理です。(router-linkタグで実行される処理と同じです)
Vueに新規登録用のルーティングを追加
/clients/newにアクセスしたときに、ClientNew.vueのテンプレートを開くようにapp.vueにルーティングを追加します。
追加するルーティングは以下になります。テンプレートのimportを忘れないようにしてください。
{
path: '/clients/new',
component: ClientNew,
name: 'clientNew'
}
app.vue
app.vueの全体像は以下のようになります。
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
import Vue from 'vue'
import VueRouter from 'vue-router'
import Top from './components/Top'
import Client from './components/clients/Client'
import Clients from './components/clients/Clients'
import ClientNew from './components/clients/ClientNew'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Top,
name: 'top',
},
{
path: '/clients',
component: Clients,
name: 'clients',
},
{
path: '/clients/:id(\\d+)',
component: Client,
name: 'client',
},
{
path: '/clients/new',
component: ClientNew,
name: 'clientNew'
}
]
})
export default {
router
}
</script>
<style lang="scss" scoped>
</style>
以上で新規登録のための準備は完了です。
ブラウザで確認
実際に、ブラウザで確認します。
エラー処理の確認
まずは、エラーが正しく表示されるか確認するため、何も入力していない状態で送信ボタンをクリックします。
エラーメッセージが表示されれば、処理が正しく行われています。
新規登録
続いて新規登録を行います。
↓ 送信ボタンをクリックすると、新規作成したクライアントのページにリンクします。
↓ 一覧ページに移動すると、データが新たに追加されていることが確認できます。
編集ページの作成
既に登録してあるデータを編集できるようにします。
編集ページ作成の流れ
Rails側にテーブルへの編集機能を追加し、Vue.js経由でPATCHメソッドでそのURLにアクセスするようにします。
- 新規登録用のVueテンプレートからform部分を別テンプレートに切り出す
- 新規登録用のVueテンプレートの編集
- Railsのresourcesにupdateのルーティングを追加
- Railsのコントローラにupdateアクションを追加
- 編集用のVueテンプレートを作成
- Vueに編集用のルーティングを追加
新規登録用のVueテンプレートからform部分を別テンプレートに切り出す
編集用画面に表示するフォーム部分は、新規登録用のフォームと共通になります。
このため、新規登録用のフォーム部分をパーシャルとして別のテンプレートに切り出します。
|- javascript
| |
| |- packs
| | |- main.js
| |
| |- app.vue
| |
| |- components
| |- Top.vue
| |
| |- clients
| |- Clients.vue
| |- Client.vue
| |- ClientNew.vue
| |- ClientForm.vue
パーシャルの作成(ClientForm.vue)
新規登録用のClientNew.vueのformタグの部分を、 ClientForm.vueに移行します。
その際、次の4か所を変更する必要があります。
- @submit.preventの値を、
"$emit('submit')"
に変更。 - dataをpropsに変更
<template>
<form @submit.prevent="$emit('submit')">
<div class="error-wrapper" v-if="errors.length != 0">
<ul v-for="error in errors" :key="error">
<li><font color="red">{{ error }}</font></li>
</ul>
</div>
<div>
<label>client_name</label>
<input v-model="client.client_name" type="text">
</div>
<div>
<label>pj_name</label>
<input v-model="client.pj_name" type="text">
</div>
<div>
<label>status</label>
<select v-model="client.status">
<option>契約前</option>
<option>進行中</option>
<option>完了</option>
</select>
</div>
<div>
<label>order_date</label>
<input v-model="client.order_date" type="date">
</div>
<div>
<label>price</label>
<input v-model="client.price" type="number" min="0">
</div>
<div>
<label>memo</label>
<textarea v-model="client.memo"></textarea>
</div>
<button type="submit">送信</button>
</form>
</template>
<script>
export default {
props:{
client: {},
errors: "",
}
}
</script>
<style lang="scss" scoped>
div:not(.error-wrapper){
display: flex;
}
label{
width: 15%;
}
input, select, textarea{
margin: 3px;
width: 30%;
border: 1px solid gray;
}
button{
margin: 10px;
display: inline-block;
padding: 0.5em 1em;
text-decoration: none;
background: #668ad8;
color: #FFF;
border-bottom: solid 4px #627295;
border-radius: 3px;
&:active{
-webkit-transform: translateY(4px);
transform: translateY(4px);
border-bottom: none;
}
}
</style>
$emitとは?
@submit.prevent
の値を、メソッド名から、$emit('submit')
に変更しています。
$emitは子テンプレートから親のテンプレートにイベントを渡すときに使います。(子→親)
$emit('イベント名', 引数,,,)
親側のテンプレートでは、v-on:イベント名
で、子テンプレートのイベント発火を検知します。値にメソッド名を指定して、メソッドを実行することが多いです。
v-on:イベント名="メソッド名"
or
@イベント名="メソッド名"
propsとは?
propsとは親のテンプレートから受け取る変数です。ここでは、clientとerrorsを受け取っています。
親から子にデータを渡すときは、呼び出しているテンプレートタグの中で、v-bindを使います。
<テンプレート名 :渡すデータ名1='変数名1' :渡すデータ名2='変数名2',,, />
「渡すデータ名」と子テンプレートのpropsで定義した変数名が連動します。
新規登録用のVueテンプレートの編集
ClientForm.vueでformタグの部分を切り出したので、それに合わせてClientNew.vueを編集します。
必要な編集内容は次の3つです。
- ClientForm.vueをコンポーネントとして読み込む
- data(errorsとclient)を子テンプレートのpropsに渡す
- 子テンプレートの$emitから受け取るイベントをセットする
<template>
<ClientForm :errors='errors' :client="client" @submit="createClient" />
</template>
<script>
import ClientForm from './ClientForm.vue'
import axios from 'axios'
export default {
components:{
ClientForm: ClientForm,
},
data(){
return {
client: {
client_name: '',
pj_name: '',
status: '',
order_date: '',
price: '',
memo: '',
},
errors: ''
}
},
methods: {
createClient(){
console.log(this.client)
axios
.post('/api/pj1/clients', this.client)
.then(response => {
let res = response.data;
this.$router.push({ name: 'client', params: { id: res.id } });
})
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
});
}
}
}
</script>
<style lang="scss" scoped>
</style>
テンプレートをコンポーネントとして読み込む
ClientForm.vueをコンポーネントとして読み込む手順は次の3つです。
- ClientForm.vueをimportする。
- export defaultの中で、componentsオプションで、コンポーネント名をつける
- templateタグ内で、読み込んだコンポーネントを呼び出す
<script>
import ClientForm from './ClientForm.vue'
export default {
components:{
ClientForm: ClientForm,
},
}
この処理が、(1)ClientForm.vueをimport、(2) export defaultの中で、componentsオプションで、コンポーネント名をつけるになります。
components: {タグ名: テンプレート名}
とすることで、読み込んだテンプレートをタグで呼び出すことができます。
<ClientForm />
data(errorsとclient)を子テンプレートのpropsに渡す
続いて、呼び出した子テンプレートのpropsにデータを渡します。タグの中でv-bindを使います(省略形は:
)
<ClientForm :errors='errors' :client="client" />
子テンプレートの$emitから受け取るイベントをセットする
子テンプレートの中で$emitで渡されているイベントを受け取れるようにします。イベントを受け取ったら、createClient
メソッドが発火するようにします。
<ClientForm :errors='errors' :client="client" @submit="createClient" />
以上で、新規登録用のテンプレートからformタグの切り出しが完了です。
ブラウザで確認
正しく読み込みができるか、/clients/newにアクセスして確かめます。
先ほど作成した内容と同じになれば、切り出しは完了です。
Railsのresourcesにupdateのルーティングを追加
編集処理を可能にするために、バックエンドとなるRailsにupdateアクション用のルーティングを追加します。
routes.rbのresourcesのonlyオプションに:update
を追記します。
Rails.application.routes.draw do
ActiveAdmin.routes(self)
(省略)
# APIコントローラへのルーティング
namespace :api, {format: 'json'} do
namespace :pj1 do
resources :clients, only: [:index, :show, :create, :update]
end
end
end
以下のようなルーティングが追加されます。
#rails routes
Prefix Verb URI Pattern Controller#Action
api_pj1_clients PATCH /api/pj1/clients/:id(.:format) api/pj1/clients#update {:format=>/json/}
PUT /api/pj1/clients/:id(.:format) api/pj1/clients#update {:format=>/json/}
Railsのコントローラにupdateアクションを追加
before_actionに:updateを追加
updateメソッドをclientモデルのインスタンスに対して実行します。clientモデルのインスタンス生成は、before_actionでset_clientメソッドを指定しているため、onlyオプションにupdateアクションを追加します。
before_action :set_client, only: [:show, :update]
updateアクションの追記
clientsコントローラにupdateアクションを追加します
#編集
def update
if @client.updat(client_params)
head :no_content
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
client_paramsメソッドの実行結果を引数としてupdateメソッドを実行します。
head :no_content
head: no_content
とは、ブラウザにステータスコード204を返す処理です。
headとは、ヘッダー(header)のみで本文(body)のないレスポンスをブラウザに送信するためのメソッドです。
:no_contentとは、シンボル形式で、ステータスコード204を表すものです。
ステータスコード204とは、リクエストが成功しかつ、現在のページから遷移する必要がないときに使います。
(参考)
clientsコントローラの全貌
clientsコントローラのコードは以下のようになります。
class Api::Pj1::ClientsController < ApiController
before_action :set_client, only: [:show, :update]
#例外処理
rescue_from Exception, with: :render_status_500
rescue_from ActiveRecord::RecordNotFound, with: :render_status_404
#一覧
def index
# @clients = Client.all
@clients = Client.select(:client_name, :pj_name, :status, :id)
render json: @clients
end
#詳細
def show
render json: @client
end
#新規登録
def create
@client = Client.new(client_params)
if @client.save
render json: @client, status: :created
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
#編集
def update
if @client.update(client_params)
head :no_content
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
private
def set_client
@client = Client.find(params[:id])
end
#ストロングパラメータの設定
def client_params
params.fetch(:client, {}).permit(:client_name, :pj_name, :status, :order_date, :price, :memo)
end
def render_status_404(exception)
render json: { errors: [exception] }, status: 404
end
def render_status_500(exception)
render json: { errors: [exception] }, status: 500
end
end
編集用のVueテンプレートを作成
フロントエンド(Vue.js)側で、編集用のVueテンプレートを作成します。
componentsのclients配下にClientEdit.vueを作成します。
|- javascript
| |
| |- packs
| | |- main.js
| |
| |- app.vue
| |
| |- components
| |- Top.vue
| |
| |- clients
| |- Clients.vue
| |- Client.vue
| |- ClientNew.vue
| |- ClientForm.vue
| |- ClientEdit.vue
ClientEdit.vueの作成
編集用のテンプレートとなるClienEdit.vueの中身を以下のようにします。form部分はClientForm.vueを読み込みます。
<template>
<div>
{{client}}
<ClientForm :errors="errors" :client="client" @submit="updateClient" />
</div>
</template>
<script>
import ClientForm from './ClientForm.vue';
import axios from 'axios';
export default {
components: {
ClientForm
},
data(){
return {
client: {},
errors: ''
}
},
mounted(){
axios
.get(`/api/pj1/clients/${this.$route.params.id}.json`)
.then(response => (this.client = response.data))
},
methods: {
updateClient(){
axios
.patch(`/api/pj1/clients/${this.client.id}`, {client: this.client})
.then(response => {
this.$router.push({ name: 'clients' });
})
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
});
}
}
}
</script>
<style lang="scss" scoped>
</style>
@submit=”updateClient”
子テンプレート(ClientForm.vue)の$emit('submit')
で発火したイベントをv-on:submit
でキャッチする処理です。(@はv-on:の省略形)
updateClientメソッドを実行します。
mounted
mountedオプションは、Vueテンプレートの描画タイミングで実行するメソッドを記述します。このページが読み込まれるときに、axiosの処理を実行します。
axios
.get(`/api/pj1/clients/${this.$route.params.id}.json`)
.then(response => (this.client = response.data))
これにより、APIのクライアント詳細ページから対象のデータを取得してきます。
取得してきたデータはtheメソッドで、変数clientに代入されます。
そして、読み込んだテンプレート(ClientForm.vue)に渡され、ブラウザ上にデフォルトとして表示されます。
axios.patch
axios.patchは、指定したURLにPATCHメソッドでアクセスする処理です。
axios.patch( 'URL', オブジェクト )
第2引数で指定したオブジェクトを渡します。
axios
.patch(`/api/pj1/clients/${this.client.id}`, {client: this.client})
.then(response => {
this.$router.push({ name: 'clients' });
PATCHメソッドの処理が成功したら、this.$router.push
で一覧ページに飛ぶようにしています。
Vueに編集用のルーティングを追加
/clients/edit/:idにアクセスしたときに、ClientEdit.vueのテンプレートを開くようにapp.vueにルーティングを追加します。
追加するルーティングは以下になります。テンプレートのimportを忘れないようにしてください。
{
path: '/clients/edit/:id(\\d+)',
component: ClientEdit,
name: 'clientEdit',
}
パラメータの後ろにある、(\\d+)
は数値のみであることを指定する正規表現です。
app.vue
app.vueの全体像は以下のようになります。
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
import Vue from 'vue'
import VueRouter from 'vue-router'
import Top from './components/Top'
import Client from './components/clients/Client'
import Clients from './components/clients/Clients'
import ClientNew from './components/clients/ClientNew'
import ClientEdit from './components/clients/ClientEdit'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Top,
name: 'top',
},
{
path: '/clients',
component: Clients,
name: 'clients',
},
{
path: '/clients/:id(\\d+)',
component: Client,
name: 'client',
},
{
path: '/clients/new',
component: ClientNew,
name: 'clientNew'
},
{
path: '/clients/edit/:id(\\d+)',
component: ClientEdit,
name: 'clientEdit',
},
]
})
export default {
router
}
</script>
<style lang="scss" scoped>
</style>
以上で新規登録のための準備は完了です。
ブラウザで確認
実際に、ブラウザで確認します。
/clients/edit/:id にアクセスして、情報が正しく読み込まれるか確認します。
↓ データを変更します。
↓ 送信ボタンをクリックすると、一覧ページに飛びます。
更新した内容が反映されていることがわかります。
以上で更新処理は完了です。
削除機能の実装
最後に削除機能を実装します。一覧ページに削除ボタンを設置し、クリックすると確認用のモーダルが開くようにします。モーダル内の削除をクリックしたときに削除が実行されるようにします。
削除機能実装の流れ
Rails側にテーブルのレコードの削除機能を追加し、Vue.js経由でDELETEメソッドでそのURLにアクセスするようにします。
- Railsのresourcesにdestroyのルーティングを追加
- Railsのコントローラにdestroyアクションを追加
- Vue.jsで削除確認用のモーダルの作成
- Vue.jsの一覧ページにモーダルを追加する
Railsのresourcesにdestroyルーティングを追加
削除処理を可能にするために、バックエンドとなるRailsにdestroyアクション用のルーティングを追加します。
routes.rbのresourcesのonlyオプションに:destroy
を追記します。
Rails.application.routes.draw do
ActiveAdmin.routes(self)
(省略)
# APIコントローラへのルーティング
namespace :api, {format: 'json'} do
namespace :pj1 do
resources :clients, only: [:index, :show, :create, :update, :destroy]
end
end
end
以下のようなルーティングが追加されます。
#rails routes
Prefix Verb URI Pattern Controller#Action
api_pj1_clients DELETE /api/pj1/clients/:id(.:format) api/pj1/clients#destroy {:format=>/json/}
Railsのコントローラにdestroyアクションを追加
before_actionに:destroyを追加
destroyメソッドをclientモデルのインスタンスに対して実行します。clientモデルのインスタンス生成は、before_actionでset_clientメソッドを指定しているため、onlyオプションにdestroyアクションを追加します。
before_action :set_client, only: [:show, :update, :destroy]
destroyアクションの追記
clientsコントローラにupdateアクションを追加します
#削除
def destroy
@client.destroy
head :no_content
end
生成したClientモデルのインスタンスをdestroyメソッドで削除します。
head :no_content
head: no_content
とは、ブラウザにステータスコード204を返す処理です。
headとは、ヘッダー(header)のみで本文(body)のないレスポンスをブラウザに送信するためのメソッドです。
:no_contentとは、シンボル形式で、ステータスコード204を表すものです。
ステータスコード204とは、リクエストが成功しかつ、現在のページから遷移する必要がないときに使います。
(参考)
clientsコントローラの全貌
clientsコントローラのコードは以下のようになります。
class Api::Pj1::ClientsController < ApiController
before_action :set_client, only: [:show, :update, :destroy]
#例外処理
rescue_from Exception, with: :render_status_500
rescue_from ActiveRecord::RecordNotFound, with: :render_status_404
#一覧
def index
# @clients = Client.all
@clients = Client.select(:client_name, :pj_name, :status, :id)
render json: @clients
end
#詳細
def show
render json: @client
end
#新規登録
def create
@client = Client.new(client_params)
if @client.save
render json: @client, status: :created
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
#編集
def update
if @client.update(client_params)
head :no_content
else
render json: { errors: @client.errors.full_messages }, status: :unprocessable_entity
end
end
#削除
def destroy
@client.destroy
head :no_content
end
private
def set_client
@client = Client.find(params[:id])
end
#ストロングパラメータの設定
def client_params
params.fetch(:client, {}).permit(:client_name, :pj_name, :status, :order_date, :price, :memo)
end
def render_status_404(exception)
render json: { errors: [exception] }, status: 404
end
def render_status_500(exception)
render json: { errors: [exception] }, status: 500
end
end
Vue.jsで削除確認用のモーダルの作成
フロントエンド(Vue.js)側で、削除確認用のモーダルの作成のVueテンプレートを作成します。
components配下にModal.vueを作成します。
|- javascript
| |
| |- packs
| | |- main.js
| |
| |- app.vue
| |
| |- components
| |- Top.vue
| |- Modal.vue
| |
| |- clients
| |- Clients.vue
| |- Client.vue
| |- ClientNew.vue
| |- ClientForm.vue
| |- ClientEdit.vue
Modal.vueの作成
Modal.vueの中身を以下のようにします。
<template>
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-body">
<slot name="body">
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button" @click="$emit('ok')">
削除
</button>
<button class="modal-default-button" @click="$emit('cancel')">
キャンセル
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-footer{
text-align: right;
}
.modal-default-button{
margin-left: 5px;
border: 1px solid gray;
padding: 2px 10px;
border-radius: 5px;
}
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
transitionタグ
transitionタグとはVue.jsのテンプレートの中で使うことで特別な意味をもつようになるタグです。トランジションやアニメーションをつけるときに使います。
v-ifやv-showが付与されているタグとあわせて使います。
v-ifやv-showにより要素が消える/表示する(遷移する)状況に合わせて以下の6つのクラスが付与されます。
クラス | 状態 |
---|---|
v-enter | enterの開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除。 |
v-enter-active | enterの活性状態。遷移中に適用。 |
v-enter-to | enterの終了状態。要素が挿入された 1 フレーム後に追加され、遷移が終了したら削除。v-enterと入れ替わりで付与される。 |
v-leave | leaveの開始状態。 |
v-leave-active | leaveの活性状態。 |
v-leave-to | leaveの終了状態。 |
leaveはenterと逆向きの遷移中に適用されるクラスです。
transitionタグにname属性をつけると、付与されるクラス名のv-
がname属性の値になります。
<transition name="modal">
上記のように、name属性の値にmodalを指定すると以下のようになります。
クラス | 状態 |
---|---|
modal-enter | enterの開始状態 |
modal-enter-active | enterの活性状態 |
modal-enter-to | enterの終了状態 |
modal-leave | leaveの開始状態 |
modal-leave-active | leaveの活性状態 |
modal-leave-to | leaveの終了状態 |
slotタグ
通常、テンプレートは使い回します。そのときに、テンプレートの中の要素を呼び出す親テンプレートに内容を変更したいときがあります。
その時は、slotタグとslot属性を使うことで、親で指定した要素を、子テンプレートに渡すことができます。
子テンプレートにslotタグを設置
親からのデータを受け取る場所として、slotタグを設置します。slotタグは複数設置することもあるため、name属性でslotタグに名前を付けます。
<slot name="スロット名">デフォルトのテキスト</slot>
親からデータが渡されない場合は、ここで指定している内容が表示されます。
親テンプレートから子のslotにデータを渡す
子のslotタグにデータを渡すには、渡したいタグの属性にslot="スロット名"
をつけるだけです。
slot属性をつけたタグが丸ごと子テンプレートのslotタグと置き換わります。
<タグ slot="スロット名">内容</タグ>
Vue.jsの一覧ページにモーダルを追加する
作成したModal.vueを一覧ページ(Clients.vue)の中でコンポーネントとして登録して呼び出します。
- componentsにModal.vueを登録
- テーブルのthにDeleteを追加
- テーブルの要素にモーダルを開くボタンを追加
- モーダルの削除ボタンがクリックされたときの削除処理を追加
<template>
<div>
<table>
<tbody>
<tr>
<th>Project Name</th>
<th>Client Nmae</th>
<th>Status</th>
<th>Details Link</th>
<th>Delete</th>
</tr>
<tr v-for="(client, index) in clients" :key="index">
<td>{{ client.client_name }}</td>
<td>{{ client.pj_name }}</td>
<td>{{ client.status }}</td>
<td><router-link :to="{ name: 'client', params: { id: client.id }}">{{ client.id }}</router-link></td>
<td>
<button @click="deleteTarget = client.id; showModal = true">Delete</button>
</td>
</tr>
</tbody>
</table>
<Modal v-if="showModal" @cancel="showModal = false" @ok="deleteClient(); showModal = false;">
<div slot="body">本当に削除しますか?</div>
</Modal>
</div>
</template>
<script>
import Modal from '../Modal.vue'
import axios from 'axios';
export default {
components:{
Modal
},
data(){
return{
clients: [],
showModal: false,
deleteTarget: -1,
errors: '',
}
},
mounted(){
this.getClients();
},
methods:{
getClients(){
axios
.get('/api/pj1/clients')
.then(response => (this.clients = response.data))
},
deleteClient(){
if (this.deleteTarget <= 0) {
console.warn('deleteTarget should be grater than zero.');
return;
}
axios
.delete(`/api/pj1/clients/${this.deleteTarget}`)
.then(response => {
this.deleteTarget = -1;
this.getClients();
})
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
});
}
}
}
</script>
<style lang="scss" scoped>
table{
text-align: center;
th{
padding: 10px;
color: #FF9800;
background: #fff5e5;
}
td{
padding: 10px;
border: 1px solid lightgray;
}
}
</style>
componentsにModal.vueを登録
Vue.jsテンプレートをcomponentsとして登録します。
import Modal from '../Modal.vue'
export default {
components:{
Modal
},
(省略)
}
components:{ Modal }
は components:{ Modal:Modal }
の省略形です。これで、templateタグの中で、ModalタグでModal.vueを呼び出すことができます。
<Modal v-if="showModal" @cancel="showModal = false" @ok="deleteClient(); showModal = false;">
<div slot="body">本当に削除しますか?</div>
</Modal>
テーブルのthにDeleteを追加
削除ボタンを設置するためのカラムをtableに追加します。
<th>Delete</th>
(省略)
<td></td>
テーブルの要素にモーダルを開くボタンを追加
上記のtdタグの中に、モーダルを開くためのボタンを追加します。
<button @click="deleteTarget = client.id; showModal = true">Delete</button>
@clickはクリックイベントです。値には、メソッドか式をいれることができます。
ここでは2つの式を指定しています。deleteTarget = client.id; showModal = true
変数deleteTargetは選択したデータのid番号を格納し、axiosのDELETEメソッドで使用します。
変数showModalの値がtrueになると、Modalタグの中のv-if="showModal"
と連動して、Modalが表示されます。
モーダルの削除ボタンがクリックされたときの削除処理を追加
export defaultのmethodsオプションの中に、モーダルの削除ボタンがクリックされたときの、削除処理を追加します。
メソッド名はdeleteClientです。
deleteClient(){
if (this.deleteTarget <= 0) {
console.warn('deleteTarget should be grater than zero.');
return;
}
axios
.delete(`/api/pj1/clients/${this.deleteTarget}`)
.then(response => {
this.deleteTarget = -1;
this.getClients();
})
.catch(error => {
console.error(error);
if (error.response.data && error.response.data.errors) {
this.errors = error.response.data.errors;
}
});
}
最初にif文で誤作動防止処理を加えています。dataオプションで定義したdeleteTargetのデフォルト値は-1です。これが、マイナスの状態のままのときにconsoleにエラーを表示します。
console.warnを使うことで表示する内容の冒頭に警告マークをつけることができます。
deleteTargetで対象の数値が渡されたときは、axiosで経由でRailsのページにDELETEメソッドでアクセスします。
削除に成功したら、deleteTargetを再度-1に戻しておきます。最後に、削除結果を現在のブラウザに反映するために、getClients
メソッドを実行します。
以上で、削除処理は完了です。
ブラウザで確認
実際に、ブラウザで確認します。
/clients にアクセスして、モーダルが正しく表示されるか確認します。
まずはキャンセル操作です。Deleteをクリックするとモーダルが表示され、キャンセルボタンをクリックするとフワっと閉じます。
続いて削除処理です。
モーダルを開いて「削除」ボタンをクリックすることで、対象のデータを削除することができました。
以上で、Railsをバックエンド、Vue.jsをフロントエンドとしたDB操作可能なアプリケーションの作成は完成です。
参考リンク
この記事の内容は、QiitaのRuby on Rails, Vue.js で始めるモダン WEB アプリケーション入門の内容を参考に、補足と実例加えながら解説をしたものです。
とても価値の高い情報を公開してくれたtatsurou313さんに感謝します。
Rails + Vue.jsの環境で、オシャレなレイアウトがサクサク組めるVuetifyを使う場合は下記をご参考ください。