19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue on RailsでActive Storageを使って画像を保存する

Posted at

はじめに

Rails API モードと Vue.js で作成した 自作ブログ で Active Storage を使う際に、画像の受け渡しでハマったので実装方法を残します。
実装するのは Active Storage を使って、eyecatch (アイキャッチ画像) 付きの Post (記事) を投稿できるようなサンプルです。

環境

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]

$ bundle exec rails --version
Rails 5.2.2

実装方法

API 部分 (Rails)

Ⅰ. サンプルアプリケーション新規作成

Rails API モードで作成します。

$ bundle exec rails new sampleapp --database=mysql --api --force
$ cd sampleapp/
$ bundle exec rails db:create

Ⅱ. Active Storage インストール

Active Storage をインストールします。

$ bundle exec rails active_storage:install
$ bundle exec rails db:migrate

Ⅲ. Post リソースの作成

今回利用する Post モデルとコントローラを作成します。

$ bundle exec rails g resource post title body:text
$ bundle exec rails db:migrate

Ⅳ. 各種ファイルの修正

各 Post に eyecatch を設定できるようにモデルを修正します。
追加する eyecatch= メソッドで、Active Storage に画像を保存します。

このメソッドは、Base64 形式で受け取った image データをエンコードし、一時的に /tmp 配下に画像ファイルを作成、作成した画像ファイルをアタッチ、その後画像ファイルを削除します。

app/models/post.rb
class Post < ApplicationRecord
  has_one_attached :eyecatch
  attr_accessor :image

  def eyecatch=(image)
    if image.present?
      prefix = image[/(image|application)(\/.*)(?=\;)/]
      type = prefix.sub(/(image|application)(\/)/, '')
      data = Base64.decode64(image.sub(/data:#{prefix};base64,/, ''))
      filename = "#{Time.zone.now.strftime('%Y%m%d%H%M%S%L')}.#{type}"
      File.open("#{Rails.root}/tmp/#{filename}", 'wb') do |f|
        f.write(data)
      end
      eyecatch.detach if eyecatch.attached?
      eyecatch.attach(io: File.open("#{Rails.root}/tmp/#{filename}"), filename: filename)
      FileUtils.rm("#{Rails.root}/tmp/#{filename}")
    end
  end
end

モデルで追加した eyecatch= メソッドに POST で受け取る画像のパラメータを渡すように修正します。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def create
    post = Post.new(post_params)

    if post.save
      post.eyecatch = post_params[:image]
      render json: post, status: :created
    else
      render json: post.errors, status: :unprocessable_entity
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :body, :image)
  end
end

Ⅴ. 動作確認

アプリケーションを起動し、curl コマンドでアイキャッチ付き Post が作成できるか確認します。

$ bundle exec rails s

$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"post": {"title": "Sample title.", "body": "Sample body.", "image": "data:application/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAYAAACALL/6AAAACXBIWXMAADXU\nAAA11AFeZeUIAAAAgUlEQVQYlZWQMQ6DMAxFnyMGJFZCc4be/yisRYyVmDN0\niTuQULCCoH9z/PLtb/hTsi8SaA1yO85ZWHxgAqb8HuVoJAX+iKPtB17L++D+\nyN6drpOa0mj7AYBg1pmz99NmSKDiVzzmKTM/uOTYMjgQNetYuKoEqj7oCHp2\nteqn2/CVvuDZJy0n3DrVAAAAAElFTkSuQmCC\n"}}' http://localhost:3000/posts

curl コマンドで作成後、Rails Console でアイキャッチが正常に追加できているか確認します。

$ bundle exec rails c
irb(main):001:0> Post.find_by(title: "Sample title.").eyecatch.attached?
=> true

# 画像のパスは以下のように取得できます。
# irb(main):002:0> app.url_for(Post.find_by(title: "Sample title.").eyecatch)
# => "画像のパス"

フロント部分 (Vue.js)

Ⅰ. Webpacker をインストール

webpacker gem を追加する。

Gemfile

gem 'webpacker', '~> 3.5'

Webpacker をインストールします。

$ bundle
$ bundle exec rails webpacker:install

Ⅱ. Vue.js をインストール

Webpacker で Vue.js をインストールします。

$ bundle exec rails webpacker:install:vue

Ⅲ. Home ページを作成

Vue.js を返すための Home ページを作成します。

$ bundle exec rails g controller Pages Home

Root パスに Home ページを設定します。

config/routes.rb
Rails.application.routes.draw do

  root 'pages#home'

end

標準の JSON ではなく、ERB を返すために ActionController::Base に修正します。

app/controllers/pages_controller.rb
class PagesController < ActionController::Base
  def home
  end
end

Home ページに利用する View を作成します。

$ mkdir -p app/views/pages/
$ touch app/views/pages/home.html.erb

Ⅳ. Vue.js を利用

Vue.js を利用するための設定を行います。

app/views/pages/home.html.erb
<%= javascript_pack_tag 'main' %>

利用する各種ファイルを作成します。

$ touch app/javascript/packs/main.js
$ touch app/javascript/packs/App.vue
app/javascript/packs/main.js
import Vue from 'vue'
import App from './App.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('main'))
  new Vue({
    el,
    render: h => h(App)
  })
})
app/javascript/packs/App.vue
<template>
  <div id="app">
    <p>投稿フォーム</p>
  </div>
</template>

Ⅴ. 投稿フォームを作成

API コールに利用する axios をインストールします。

$ yarn add axios

画像投稿するフォームを用意します。
画像は POST する前に Base64 にデコードしています。

app/javascript/packs/App.vue
<template>
  <div id="app">
    <p>投稿フォーム</p>
    <form v-on:submit.prevent="postItem()">
      <p>
        <label>Title</label>
        <input name="post.title" type="text" v-model="post.title"><br />
      </p>
      <p>
        <label>Body</label>
        <input name="post.body" type="text" v-model="post.body"><br />
      </p>
      <p>
        <label>画像</label>
        <input name="uploadedImage" type="file" ref="file" v-on:change="onFileChange()"><br />
      </p>
      <input type="submit" value="Submit">
    </form>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      post: {},
      uploadedImage: ''
    }
  },
  methods: {
    onFileChange() {
      let file = event.target.files[0] || event.dataTransfer.files
      let reader = new FileReader()
      reader.onload = () => {
        this.uploadedImage = event.target.result
        this.post.image = this.uploadedImage
      }
      reader.readAsDataURL(file)
    },
    postItem() {
      return new Promise((resolve, _) => {
        axios({
          url: '/posts',
          data: {
            post: this.post
          },
          method: 'POST'
        }).then(res => {
          this.post = {}
          this.uploadedImage = ''
          this.$refs.file.value = ''
          resolve(res)
        }).catch(e => {
          console.log(e)
        })
      })
    }
  }
}
</script>

Ⅵ. 動作確認

アプリケーションを起動し、投稿フォームから画像を投稿します。
http://localhost:3000/

$ bundle exec rails s
img1.png

Rails Console でアイキャッチが正常に追加できているか確認します。

$ bundle exec rails c
irb(main):001:0> Post.last.eyecatch.attached?
=> true

参考記事

最後に

読んでいただいてありがとうございます。
間違っている点などがありましたら、ご指摘いただけると喜びます!

19
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?