π Upload Files from Vue.js to Rails with ActiveStorage
Simple enough, isn't it ? Well, no, but yes, but maybe I'll just be honest there and confess this took me way longer than I'd love to admit.
The goal of the game is to create an Item from a Vue.js user form, and store it quietly in our Ruby on Rails API, using ActiveStorage. An item will have a name, description, and of course, the user-uploaded picture ! We're going to build a Vue.js component and the Rails controller. Let's dance !
Our Vue.js Form Component
The HTML Template
<template>
<div>
<h2>Add an item</h2>
<form enctype="multipart/form-data">
<p>Name: </p><input v-model="inputName">
<p>Description :</p><textarea v-model="inputDescription"></textarea>
<p>Picture :</p><input type="file" ref="inputFile" @change=uploadFile()>
<button @click=createItem>Create this Item !</button>
</form>
</div>
</template>
The HTML part is simple enough. Make sure to add that enctype="multipart/form-data"
. Another possibility is to just add Content-Type: multipart/form-data
to the header of our javascript POST request. But I prefer adding complexity to HTML instead.
Please also notice how we activate the function uploadFile()
when a change is detected on the file upload button.
The Javascript
export default {
name: 'itemsForm',
// Here is the data we get from our HTML Form.
data () {
return {
inputName: "",
inputDescription: "",
inputPicture: null
}
},
methods: {
// Saving the file in our data to send it !
uploadFile: function() {
this.inputPicture = this.$refs.inputFile.files[0];
},
// Collecting everything inside our FormData object
createItem: function() {
const params = {
'name': this.inputName,
'description': this.inputDescription,
'picture': this.inputPicture
}
let formData = new FormData()
Object.entries(params).forEach(
([key, value]) => formData.append(key, value)
)
// Finally, sending the POST request with our beloved Axios
axios.post('/item', formData)
}
}
}
No black magic there, everything is simple. We iterate over the params to add them to a FormData object, then we send it.
Our Rails Controller
class ItemsController < ApplicationController
def create
item = Item.create item_params
# Attach picture to our item, if available
attach_main_pic(item) if admin_params[:picture].present?
# Reply with success if the object was saved, or failure if it was not.
if item.persisted?
render json: item, status: 200
else
render json: item, status: 400
end
end
private
def attach_main_pic(item)
item.picture.attach(admin_params[:picture])
end
def item_params
{
name: admin_params[:name],
description: admin_params[:description],
}
end
def admin_params
params.permit(
:name,
:description,
:picture
)
end
end
Don't forget to add post 'items' => 'items#create'
to your Rails routes. You might also need to deactivate the parameter wrapping in the initializers.
What else to do ?
I kept the code simple there, but obviously you should add validations in the controller, both on size and file types. You don't want just any crap taking all your precious server space !
If you want multiple file upload, Check out this link !