Help us understand the problem. What is going on with this article?

base64でエンコードされた画像をActive Storageで保存する

More than 1 year has passed since last update.

はじめに

Rails5.2系からActive Storageというファイルアップロード機能が追加されました。Rails5.1以前のバージョンではCarrierwaveなどのgemを使ってファイルアップロード機能を実装していたのではないでしょうか。本記事はActive Storageを用いて画像ファイルをbase64形式でやりとりする方法を記します。

本記事はこちらの記事を参考にしています。

今回のゴール

  • base64形式でエンコードされた画像をデコードし.png形式などで保存する

環境

  • os: macOS X High Sierra
  • ruby: 2.5.0
  • rails: 5.2.0

実装

本記事ではサンプルアプリを開発しながら行います。

準備

1. rails new

今回はDBにmysqlを使用しapiモードで作成しようと思います。(DBのパスワード等は各々で)

$ rails new test-app -d mysql --api

2. jbuilderのインストール

apiの作成ということでjsonを返す際にjbuilderというテンプレートエンジンを使います。
Gemfileファイル内にコメントアウトされているためそれを外しjbuilderのインストールを行います。

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'

コメントアウトを外したらbundle installを実行します。

$ bundle install --path vendor/bundle

3. Active Storageのインストール

Active Storageのインストールを行います。

$ rails active_storage:install

config/environments/development.rbconfig/storage.ymlが以下のようになっているか確認してください。

config/environments/development.rb
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
config/storage.yml
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

4. book modelの作成

book modelの作成を行います。作成を行ったら一度migrateしましょう。

$ rails g resource book title:string
$ rails db:migrate

5. viewの作成

viewの作成を行います。test_app/app/views/配下にbooksディレクトリを作成します。作成したbooksディレクトリ配下にindex.json.jbuilderファイルを作成します。

$ mkdir test_app/app/views/books
$ touch test_app/app/views/index.json.jbuilder

6. ファイルをbookモデルに紐付ける

画像をbookと紐付けるためにhas_one_attachedを使いファイルをbookモデルに添付します。

has_one_attachedマクロは、レコードとファイルの間に1対1のマッピングを設定します。各レコードには1つのファイルを添付できます。

model/book.rb
class Book < ApplicationRecord
  has_one_attached :book_image
end

これで準備完了です。

base64でエンコードされた画像をデコードしActive Storageで保存

model/book.rbにpase_base64メソッドを作成します。base64形式で画像データが送られてきた際にcreate_extensionで正規表現を用いて拡張子が何であるかを調べます。次に正規表現でdata:image/png;base64の後の部分を抜き出しデコードを行います。test_app/tmp配下に一時的に保存し、保存した画像データをattach_imageでActive Storageの方で保存を行います。保存を行うとtest_app/tmp配下に一時的に保存してあった画像ファイルを削除します。

app/model/book.rb
class Book < ApplicationRecord
  has_one_attached :book_image
  attr_accessor :image

  def parse_base64(image)
    if image.present? || rex_image(image) == ''
      content_type = create_extension(image)
      contents = image.sub %r/data:((image|application)\/.{3,}),/, ''
      decoded_data = Base64.decode64(contents)
      filename = Time.zone.now.to_s + '.' + content_type
      File.open("#{Rails.root}/tmp/#{filename}", 'wb') do |f|
        f.write(decoded_data)
      end
    end
    attach_image(filename)
  end

  private

  def create_extension(image)
    content_type = rex_image(image)
    content_type[%r/\b(?!.*\/).*/]
  end

  def rex_image(image)
    image[%r/(image\/[a-z]{3,4})|(application\/[a-z]{3,4})/]
  end

  def attach_image(filename)
    book_image.attach(io: File.open("#{Rails.root}/tmp/#{filename}"), filename: filename)
    FileUtils.rm("#{Rails.root}/tmp/#{filename}")
  end
end

作成したpase_base64メソッドを画像データ保存時に使うためにCreate部分を以下のようにします。また、book_paramsimageを追加します。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  # POST /books
  def create
    @book = Book.new(book_params)

    if @book.save
      @book.parse_base64(params[:image])
      render json: @book, status: :created, location: @book
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

  private

  def book_params
    params.require(:book).permit(:title, :image)
  end
end

保存した画像をblob_urlで取得

jbuilderでjsonをいい感じに表示するために以下のようにします。

views/books/index.json.jbuilder
if @books.present?
  json.books do
    json.array!(@books) do |book|
      json.extract! book, :id, :title
      json.image rails_blob_url(book.book_image) if book.book_image.attached?
    end
  end
end

現状のままだとlocalhost:3000/books にアクセスした際index.json.jbuilderを参照しません。また、画像は保存されていますが画像データが多くなるとN+1問題が発生します。そのため以下のようにindexを書き直しましょう。with_attached_book_imageで何をしているかはこちらを参照してください。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  # GET /books
  def index
    @books = Book.with_attached_book_image
    render 'index', formats: 'json', handlers: 'jbuilder'
  end
end

localhost:3000/books にアクセスして確認して見ましょう。
スクリーンショット 2018-10-10 0.54.21.png

まとめ

APIなどを作成する際にbase64形式で画像データをやり取りすることはあると思います。少しでも役に立ったら幸いです。

参考にしたもの

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした