Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File uploading with ActiveStorage in Vue.js

I'm looking to add file uploads to a Card model (including single and multiple files) using the new Rails 5.2 ActiveStorage feature. I'm stronger in Rails and am using Vue.js to pull this off - previously I would use Paperclip to handle this. However, I've setup the config/storage.yml file and necessary migrations for ActiveStorage. I've also set the association between on the Card and updated the CardController to permit files: [].

The card.vue component that I created currently works well; a Card can have a title and description, and the description can be updated. These records persist to the database. My problem is figuring out how to bind file uploads to the card, and the logic required to upload multiple files on save.

Currently I'm using <input name="files" type="file" data-direct-upload-url="/rails/active_storage/direct_uploads" direct_upload="true" /> to create the input field in the card. However, after selecting a PDF image from my local machine and clicking save, the logs show that nothing happens [in regards to inserting a new row in active_storage_attachments or creating a blob]. How can I expand the save method to accept the file?

card.rb

class Card < ApplicationRecord
  has_many_attached :files
end

CardController

class CardsController < ApplicationController
  private
    def card_params
      params.require(:card).permit(:list_id, :title, :position, :description, files: [])
    end
end

card.vue

<template>
  <div>
    <div @click="editing=true" class="card card-body">
      <h4>
        {{card.title}}
      </h4>
    </div>

    <div v-if="editing" class="modal-backdrop show"></div>

    <div v-if="editing" @click="closeModal" class="modal show" style="display: block">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <div>
              <h4>
                {{card.title}}
              </h4>
            </div>
          </div>
          <div class="modal-body">
            <div>
              <h5>{{card.description}}</h5>
            </div>
            <textarea v-model="description" class="form-control"></textarea>
          </div>
          <div class="modal-footer">
            <input name="files" type="file" data-direct-upload-url="/rails/active_storage/direct_uploads" direct_upload="true" />
            <button @click="save" type="button" class="button button-secondary">Save changes</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  props: ["card", "list"],

  data: function() {
    return {
      editing: false,
      title: this.card.title,
      description: this.card.description,
      files: []
    }
  },

  methods: {
    save: function() {
      var data = new FormData
      data.append("card[title]", this.title)
      data.append("card[description]", this.description)

      Rails.ajax({
        url: `/cards/${this.card.id}`,
        type: "PATCH",
        data: data,
        dataType: "json",
        success: (data) => {
          const list_index = window.store.lists.findIndex((item) => item.id == this.list.id)
          const card_index = window.store.lists[list_index].cards.findIndex((item) => item.id == this.card.id)
          window.store.lists[list_index].cards.splice(card_index, 1, data)

          this.editing = false
        }
      })
    }
  }
}
</script>
like image 783
VegaStudios Avatar asked Nov 24 '18 09:11

VegaStudios


1 Answers

Try to change your input name to fit with your permitted params:

<input name="card[files][]" type="file" ... />

Updated:

As I answered you need to change your params to fit with your permitted ones.
Now using Vue, you can remove the name and add the files to the FormData on change event like this:

<template>
  <div>
    ...
    <div class="modal-footer">
      <input ref="files" type="file" @change="onFilesChange"
        data-direct-upload-url="/rails/active_storage/direct_uploads"
        direct_upload="true" multiple />
      <button @click="save" type="button" class="button button-secondary">Save changes</button>
    </div>
    ...   
  </div>
</template>

<script>

export default {
  props: ["card", "list"],

  data: function() {
    return {
      editing: false,
      title: this.card.title,
      description: this.card.description,
      form: new FormData
    }
  },

  methods: {
    onFilesChange: function() {
      let files = this.$refs.files.files
      for(let file of files) {
        this.form.append('card[files][]', file)
      }
    },
    save: function() {
      this.form.append("card[title]", this.title)
      this.form.append("card[description]", this.description)

      Rails.ajax({
        url: `/cards/${this.card.id}`,
        type: "PATCH",
        data: this.form,
        dataType: "json",
        success: (data) => {
          // ...
          this.editing = false
        }
      })
    }
  }
}
</script>

Note: don't forget the multiple property on input if you want to upload multiple files.

like image 74
Sovalina Avatar answered Oct 05 '22 06:10

Sovalina