Vue+Railsでファイルアップロード機能を作成してみる

今回はRailsとVueを使用してファイルアップロード機能について作成したいと思います。

開発環境

  • vue:2.3.4
  • typescript:2.2.2
  • ruby2.4.2
  • rails5.2.0

投稿記事を例に説明していきます。

投稿用のテーブル作成

投稿のタイトル、本文、サムネイル画像用のテーブルを用意します。

rails g model Post title:string content:string image_name:string

controllerの設定

アップロードするのに必要な箇所だけを載せます。

class PostsController < ApplicationController

  def new
    @post = Post.new
  end

  def create
    if params[:post][:image_name].present?
      @post = Post.new(
        title: params[:post][:title],
        content: params[:post][:content],
        image_name: params[:post][:image_name].original_filename,
        user_id: current_manager.id
      )
     output_path = Rails.root.join('public/images', params[:post][:image_name].original_filename)
      File.open(output_path, 'w+b') do |fp|
        fp.write(params["post"]["image_name"].read)
      end
    else
      @post = Post.new(
        title: params[:post][:title],
        content: params[:post][:content],
        image_name: "no-image.png",
      )
    end

    redirect_to root_path
  end
end

DBにはparamsで取得したimage_nameを直接いれようとするとエラーになります。 アップロードされたファイル(params[:image_name)は以下のようにActionDispatch::Http::UploadedFileクラスとなっています。

=> #<ActionDispatch::Http::UploadedFile:0x00007fc3222860a8
 @content_type="image/jpeg",
 @headers="Content-Disposition: form-data; name=\"post[image_name]\"; filename=\"img_04.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="img_04.jpg",
 @tempfile=#<File:/var/folders/t0/pl2n_7gx5vdbc8w1dww0j8400000gn/T/RackMultipart20180721-5143-1qm64n6.jpg>>

params[:image_name].original_filenameでファイル名を取得します。

その後、画像を保存する場所のパスを作成します。

output_path = Rails.root.join('public/images', params[:post][:image_name].original_filename)

次に上記で取得したパスに画像を保存します。

File.open(output_path, 'w+b') do |fp|
  fp.write(params["post"]["image_name"].read)
end

ファイルの読み込みと書き込み処理にはFileクラスがあります。 ファイル本体のデータを保存するためにreadメソッドを使用します。 これでrails側の設定は終わりです。

vueとrailsの連結部分

今回はrails側でform_tag等は使用しないでvue側でpostリクエストをするのでrails側で作成したトークンをvueに渡す必要があります。 詳細は以前自分が書いた記事があるので参考にしてみてください。

top-men.hatenablog.com

vue側の設定

<template>
  <div class='wrapper'>
    <form action='/posts' method="post" enctype="multipart/form-data">
    <input
      name="authenticity_token"
      type="hidden"
      :value="token">
      <p class="postTitle">
        <input
          type='text'
          placeholder="タイトル"
          class="postTitleField"
          v-model="post.title"
          name="post[title]"
          >
      </p>
      <input type="file" name="post[image_name]">
      <p class="tag"><input type='text' class="tag_input" placeholder="タグを入力してください" name="tag_name"></p>
      <button class="releaseBtn">公開する</button>
    </form>
   </div>
</template>


<script lang="ts">
import { Vue, Prop } from "vue-property-decorator";
import Post from "./data/post";

export default class New extends Vue {
  @Prop() public post: Post;
  @Prop() public token: string;
}
</script>

ここでは親からアクセストークンをpropsで受け取りinputタグのvalueに代入しているところがないとinvalid tokenとエラーになります。

まとめ

以上でgemを使用しないで簡単なアップロードの機能ができました。 最初は画像データの中身が文字列として保存されてしまったりと躓きましたが、なんとか完成することができました。