はじめに
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.rb
とconfig/storage.yml
が以下のようになっているか確認してください。
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
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つのファイルを添付できます。
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
配下に一時的に保存してあった画像ファイルを削除します。
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_params
にimage
を追加します。
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をいい感じに表示するために以下のようにします。
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
で何をしているかはこちらを参照してください。
class BooksController < ApplicationController
# GET /books
def index
@books = Book.with_attached_book_image
render 'index', formats: 'json', handlers: 'jbuilder'
end
end
localhost:3000/books にアクセスして確認して見ましょう。
まとめ
APIなどを作成する際にbase64形式で画像データをやり取りすることはあると思います。少しでも役に立ったら幸いです。