Edited at

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


はじめに

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形式で画像データをやり取りすることはあると思います。少しでも役に立ったら幸いです。


参考にしたもの