#はじめに
CarrierWaveの導入はRailsアプリケーションでは簡単にできるのですが、SPAでは少し苦労したのでまとめておきます。
本記事では、ローカル(public/uploader以下)にファイルをアップロードするデフォルトの設定で進めます。
また、CarrierWaveの基本的な説明は省略します。
#環境
- Ruby 3.0.0
- Rails 6.1.3
- Vue.js 2.6.12
- Vue CLI 4.5.1
CarrierWaveのインストール
この記事ではJSONデータの作成にjbuilderを使うので、一緒にインストールしておきます。
gem 'carrierwave'
gem 'jbuilder'
$ bundle install
Railsの設定
$ rails g uploader Image
$ rails g model Post image:string
$ rails db:migrate
mount_uploader :image, ImageUploader
Rails.application.routes.draw do
scope format: 'json' do
resources :posts
end
end
app/uploaders/image_uploader.rbはデフォルト設定のまま使います。
ただし、このままファイルをアップロードしてVue.js側で取得するとURLは
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"となり表示できません。
なので、config/initializers/carrierwave.rbを作成して、
CarrierWave.configure do |config|
config.asset_host = 'http://localhost:3000'
end
これで、"http://localhost:3000/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"となり表示できるようになります。
続いて、Postsコントローラーを作ります。
class PostsController < ApplicationController
def index
posts = Post.all
@images = posts.map { |post| post.image.url }
end
def create
@post = Post.new(post_params)
if @post.save
render json: :created
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
def post_params
params.require(:post).permit(:image)
end
end
ポイントはpost.image.url
です。
Rails側でurlまで取得しておかないと、Vue.jsで表示したときにconsoleに警告が出ます。
app/views/posts/index.json.jbuilderを作成してRails側は完了です。
json.images do
json.array! @images
end
Vue.jsの設定
まずは、投稿フォームを作ります。
<input type="file">
ではv-modelは使えないので、@changeでファイルを取得します。
<template>
<div>
<input type="file" @change="setImage">
<button @click="postImage">
</div>
</template>
<script>
import axios from '@/axios'
export default {
data () {
return {
image: ''
},
methods: {
setImage (e) {
this.image = e.target.files[0]
},
postImage () {
const formData = new FormData()
formData.append('post[image]', this.image)
axios.post('/posts', formData)
}
}
}
</script>
Rails側でStrong Parametersを設定しているので、パラメータを
{"post" => {"image"=>"xxxxx/xxxx/xxxx"}}
で渡すために、formData.append('post[image]', this.image)
としています。
最後に投稿一覧を作成します。
<template>
<div>
<div v-for="(image, index) in images" :key="index">
<img :src="image">
</div>
</div>
</template>
<script>
import axios from '@/axios'
export default {
data () {
return {
images: []
},
created () {
axios.get('/posts').then(response => { this.images = response.data.images })
}
}
</script>