Vue.jsとRailsAPIを使って複数のCSVファイルをそれぞれのテーブルにインポートする必要があったので記録しておきます。
ご留意ください
Rails,Vue.js学習中の初学者が備忘録を兼ねて書いています。
内容に誤りを含む可能性・さらに良い手法がある可能性が多分にありますので、参考にする際はその点ご留意ください。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
実現したいこと
inputボックスを用意し、選択したCSVファイル(2種類)をそれぞれ用意したテーブルにaxiosを使ってPOSTしたい。
csv1 ⇨ code_listsテーブル
csv2 ⇨ financialsテーブル
テーブルの定義は下記の通りです。
create_table "code_lists", force: :cascade do |t|
t.string "edinet"
t.string "securities"
t.string "company"
t.string "sector"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["company"], name: "index_code_lists_on_company"
t.index ["edinet"], name: "index_code_lists_on_edinet", unique: true
t.index ["securities"], name: "index_code_lists_on_securities"
end
create_table "financials", force: :cascade do |t|
t.string "edinet"
t.date "rec_date"
t.string "account_name"
t.float "value"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_name"], name: "index_financials_on_account_name"
t.index ["edinet", "rec_date", "account_name"], name: "index_financials_on_edinet_and_rec_date_and_account_name", unique: true
t.index ["edinet"], name: "index_financials_on_edinet"
end
下準備
事前にライブラリを追加します。
require 'csv' #追記
Gemのrooを追加してbundle install
します
gem 'roo'
axios-post
時にcsrf
トークン対策が必要な為、別途プラグインを設定します。
plugins
フォルダを新たに作成してください。
const VueAxiosPlugin = {}
export default VueAxiosPlugin.install = function(Vue, { axios }) {
const csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
axios.defaults.headers.common = {
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": csrf_token
}
Vue.axios = axios
Object.defineProperties(Vue.prototype, {
axios: {
get () {
return axios
}
}
})
}
エントリーファイルにプラグインをimportします
import Vue from "vue/dist/vue.esm";
import axios from "axios"; // 追加
import VueAxiosPlugin from "./plugins/vue-axios"; // 追加
import App from "./components/App.vue";
Vue.use(VueAxiosPlugin, { axios: axios }) // 追加
new Vue({
el: "#app",
render: h => h(App),
})
ルーティング
ルーティングは次のように設定しました。
必要な箇所のみ記載しています。
それぞれRailsAPIのルーティング設定にしています。
code_listsテーブル用のcsvファイルはapi/code_lists/importへ
financialsテーブル用のcsvファイルはapi/financials/importへPOSTします。
Rails.application.routes.draw do
(上記省略)
namespace :api, format: 'json' do
resources :code_lists do
post :import, on: :collection
end
end
namespace :api, format: 'json' do
resources :financials do
post :import, on: :collection
end
end
(以下省略)
end
View
ビューは次のようにしました。
<template>
<div>
<div class="import-form">
<input @change="selectedFile" type="file" name="file">
</div>
<div class="import-form">
<button @click="upload('/api/code_lists/import')" type="submit">コードリストのアップロード</button>
</div>
<div class="import-form">
<button @click="upload('/api/financials/import')" type="submit">財務データのアップロード</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
data: function(){
return {
uploadFile: null
};
},
methods: {
selectedFile: function(e) {
// 選択された File の情報を保存しておく
e.preventDefault();
let files = e.target.files;
this.uploadFile = files[0];
},
upload: function(url) {
// FormData を利用して File を POST する
let formData = new FormData();
formData.append('file', this.uploadFile);
// let config = {
// headers: {
// 'content-type': 'multipart/form-data'
// }
// };
axios
.post(url, formData)
.then(function(response) {
// response 処理
})
.catch(function(error) {
// error 処理
})
}
}
}
</script>
inputボックスのファイル選択後にselectedFileメソッドでファイルをuploadFileに保存します。そして、buttonのクリックイベントでuploadメソッドが実行されます。uploadメソッドの引数のurl(先ほどroutesで設定した2つのurl)へファイルをPOSTします。POSTする際に事前に保存したuploadFileをFormDataオブジェクトに渡している事がポイントです。
今回はconfigは使用しなかった為コメントアウトしました。
コントローラーとモデル
今回は2つのcsvファイルをそれぞれのテーブルにImportする実装となり、それぞれのモデル毎に実装が必要となりますが、ほぼ同じ内容となる為Financial Modelの実装のみ記載しておきます。
まずコントローラについて。
class Api::FinancialsController < ApplicationController
def import
Financial.import(params[:file])
end
end
コントローラはFinancial Classのクラスメソッドを呼び出すだけとなります。
続いて、financialモデルの記述は以下の通りです。
class Financial < ApplicationRecord
validates :edinet, presence: true
validates :rec_date, presence: true
validates :account_name, presence: true
validates :value, presence: true
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
# IDが見つかれば、レコードを呼び出し、見つかれなければ、新しく作成
financial = find_by(edinet: row["edinet"], rec_date: row["rec_date"], account_name: row["account_name"]) || new
# CSVからデータを取得し、設定する
financial.attributes = row.to_hash.slice(*updatable_attributes)
# 保存する
financial.save
end
end
def self.updatable_attributes
["edinet", "rec_date", "account_name", "value"]
end
end
インポートしたいCSVのカラム名をupdatable_attributesに入力。
またfind_byで既に取り込み済みのレコードかどうかを確認しており、取り込み済みの場合は上書きされます。
最後に
現何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
上場企業の財務データを活用したアプリケーションを開発中です。フロントエンドをVue.js、バックエンドはRailsAPIを使って開発を進めています。引き続き、得られた知見は記事にしてまとめていきたいと思います。
ここまでお付き合いいただきありがとうございました!
参考にさせていただいた記事:
csrf対策:【Vue】axiosで、デフォルトでCSRFトークンを設定できるようにする
フロントエンド実装:Vue.js でファイルをポストしたいとき
バックエンド実装:【Ruby on Rails】CSVインポート