はじめに
プログラミングを始めて5ヶ月が経過しました。時が経つのは本当に早いですね。。。
先日1つ目のポートフォリオが完成し、2つ目のポートフォリオ制作に取り掛かっています。
画像をアップロード用のgemを調べていたところ、Shrineというイケてそうなgemを見つけました。
1つ目ではRailsチュートリアルに従って、CarrierWaveを用いていたのですが、2作品目では違うものを扱ってみたい!という考えから、今回はこのShrineを用いてアップロード機能を実装することにしました。
shrine gemとは?
- Shrine (公式)
- shrine (GitHub)
- ファイル(主に画像)をアップロードするためのツール
- 特徴としては、CarrierWaveよりもシンプルであり、必要に応じてプラグインを追加することでリサイズやAWS S3へのダイレクトアップロードなどの機能を実装可能
- その他には、英語情報も含めて情報が多いことや、開発が止まってない(頻繁にcommitやプルリクエストされている)点が魅力的
開発環境
- Ruby 2.5.1
- Rails 5.2.2
- Devise 4.5.0
- Shrine 2.14.0
前提
- 認証系にはDeviseを用いており、
current_user
メソッドなどが使用できる状態であるとします。 - ユーザープロフィール画像をアップロードしたい、という状況を考えていきます。
shrine gemの使い方
主に以下3つの兄弟記事を参考にShrineの導入を進めました。
今回は、下記3点について説明していきます。
- Shrineの導入、
/public/uploads
フォルダへの格納機能の実装 - 画像リサイズ機能の導入・バリデーション設定
- AWS S3へのダイレクトアップロード機能の導入・設定
1. Shrineの導入
gemの追加
まず、shrine gem
を追加し、$ bundle install
を実行します。
gem 'shrine'
設定ファイルの作成
次に、$ touch config/initializers/shrine.rb
にてShrineの設定ファイルを作成し、中身を記述していきます。
これで、画像データを送信した時に/public/uploads/cache
フォルダ(一時保存用)および/public/uploads/store
フォルダ中(永久保存用)に画像が入るようになります。
また、cached_attachment_data
プラグインを追加することで、フォーム中で= f.hidden_field :image, value: current_user.cached_image_data
のように、キャッシュデータを送信できるようになります。
require "shrine"
require "shrine/storage/file_system"
# アップロードするディレクトリの指定
Shrine.storages = {
cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store")}
# 使用するプラグインの宣言
Shrine.plugin :activerecord
Shrine.plugin :cached_attachment_data
Userモデルにimage_data
属性を追加
続いて、$ rails g migration add_column_to_users image_data:text
でUserモデルにimage_data
属性を追加します。
ここで、image_data
属性のデータ型はtext
である点に気をつけて下さい。
(string
にするといざデータを送信した時に文字数制限に引っ掛かりエラーが出ました。)
その後、忘れずに$ rails db:migrate
を実行します。
ImageUploaderモデルの作成
app/uploaders/image_uploader.rb
ファイルを作成し、中身を記述します。
基本部分の導入ではこれだけで問題ありません。
class ImageUploader < Shrine
end
Userモデルの編集
Userモデルに実装したアップローダについて追記します。
ここで、[:image_data]
ではなく、[:image]
とすることに注意です。
class User < ApplicationRecord
include ImageUploader[:image] # ここを追記、[:image_data]としないこと
・
・
・
end
Strong Parameterの編集
Strong Parameterに:image
を追加して、画像データが保存できるように設定します。
ここでも、[:image_data]
ではなく、[:image]
とすることに注意です。
また、今回はDeviseを使用しているので、app/controllers/application_controller.rb
中のDevise用のStrong Parameter中に記述していますが、時と場合によってコントローラは変えて下さい。
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_in, keys: [:name, :email, :password, :password_confirmation])
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :email])
# 今回は以下に追記、[:image_data]としないこと
devise_parameter_sanitizer.permit(:account_update, keys: [:name, :email, :password, :password_confirmation, :image])
end
end
ビューの実装
対象のビューを作成し、実装していきます。
今回はDeviseで自動生成されたユーザー編集ページ(app/views/devise/registrations/edit.html.slim
)に記述しています。
f.label
の部分だけimage_data
となることに注意して下さい。
また、f.hidden_field
にてキャッシュデータを送信するようにしています。
(.cached_image_data
は前述のcached_attachment_data
プラグインがなければエラーが出ます。)
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {method: :put}) do |f|
・
・
・
= f.label :image_data, "プロフィール画像"
= f.hidden_field :image, value: current_user.cached_image_data
= f.file_field :image
・
・
・
= f.submit "ユーザー情報を更新"
以上で画像をアップロード機能は実装できました。
引き続き、画像のリサイズとAWS S3へのダイレクトアップロードについて説明していきます。
2. 画像リサイズ・バリデーション設定
画像のリサイズ・バリデーションについては、shrine (GitHub)中に全て書いているため、を見ながら進めるとスムーズです。
ImageMagickのインストール
$ brew install imagemagick
image_processing gemの追加
以下のgemを追加して$ bundle install
を実行します。
gem 'image_processing'
gem 'mini_magick'
ImageUploaderモデルの編集
先程作成したapp/uploaders/image_uploader.rb
の中身を記述していきます。
require "image_processing/mini_magick"
class ImageUploader < Shrine
plugin :processing # allows hooking into promoting
plugin :versions # enable Shrine to handle a hash of files
plugin :delete_raw # delete processed files after uploading
process(:store) do |io, context|
versions = { original: io } # retain original
io.download do |original|
pipeline = ImageProcessing::MiniMagick.source(original)
versions[:large] = pipeline.resize_to_limit!(800, 800)
versions[:medium] = pipeline.resize_to_limit!(500, 500)
versions[:small] = pipeline.resize_to_limit!(300, 300)
end
versions # return the hash of processed files
end
end
これによって、以下のようなハッシュが生成されるようになります。
そして、この設定をすることで**photo.image[:medium].url
のような形でハッシュに格納された画像データをリサイズしてから取り出すことが可能になります。**
photo.image_data #=>
# '{
# "original": {"id":"9sd84.jpg", "storage":"store", "metadata":{...}},
# "large": {"id":"lg043.jpg", "storage":"store", "metadata":{...}},
# "medium": {"id":"kd9fk.jpg", "storage":"store", "metadata":{...}},
# "small": {"id":"932fl.jpg", "storage":"store", "metadata":{...}}
# }'
photo.image #=>
# {
# :original => #<Shrine::UploadedFile @data={"id"=>"9sd84.jpg", ...}>,
# :large => #<Shrine::UploadedFile @data={"id"=>"lg043.jpg", ...}>,
# :medium => #<Shrine::UploadedFile @data={"id"=>"kd9fk.jpg", ...}>,
# :small => #<Shrine::UploadedFile @data={"id"=>"932fl.jpg", ...}>,
# }
photo.image[:medium] #=> #<Shrine::UploadedFile>
photo.image[:medium].url #=> "/uploads/store/lg043.jpg"
photo.image[:medium].size #=> 5825949
photo.image[:medium].mime_type #=> "image/jpeg"
バリデーション
前述のapp/uploaders/image_uploader.rb
にバリデーションの項目を追記することでバリデーションが設定されます。
途中にあるplugin :validation_helpers
を入れ忘れないように注意して下さい。
require 'image_processing/mini_magick'
class ImageUploader < Shrine
plugin :processing
plugin :versions
plugin :delete_raw
plugin :validation_helpers # ここを追記
process(:store) do |io, context|
versions = {original: io}
io.download do |original|
pipeline = ImageProcessing::MiniMagick.source(original)
versions[:large] = pipeline.resize_to_limit!(800, 800)
versions[:medium] = pipeline.resize_to_limit!(500, 500)
versions[:small] = pipeline.resize_to_limit!(300, 300)
end
versions
end
# 以下を追記
Attacher.validate do
validate_max_size 5 * 1024 * 1024, message: '5MBを超える画像はアップロードできません。'
validate_mime_type_inclusion %w(image/jpeg image/png)
end
end
以上で画像のリサイズとバリデーションの実装は完了です。
3. AWS S3へのダイレクトアップロード
AWS S3バケットの作成
最初に、AWS側でバケット作成などをする必要がありますが、ここでは参考にしたURLだけ載せて割愛します。
(S3バケットの作成方法についての参考URL)
CORSの設定
CORSについては以下の通りです。
CORSはCross-Origin Resource Sharingの略で「特定のドメインにロードされたクライアントウェブアプリケーションが異なるドメイン内のリソースと通信する方法を定義します」
ShrineでS3に画像をアップロードする
上記で作成したS3バケットにおいて、「アクセス制限」→「CORSの設定」→「CORS構成エディター」の順でクリックしていき下記の通り入力して「保存」をクリックします。
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>https://your.domain.com</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Gemの追加
以下のgemを追加して$ bundle installを実行します。
gem "aws-sdk-s3"
group :development, :test do
gem 'dotenv-rails'
end
.env
にS3のアクセスキーなどの情報を記述
アクセスキー等は流出するとAWSを不正に悪用されてしまう危険があるので、晒さないようにします。
$ touch .env
でアプリケーションのルートディレクトリに.env
ファイルを追加してそのファイルに以下のように記述することで一安心です。
なお、この時に、.gitignore
に.env
を追加しておかないとプッシュした時にパスワード等が結局晒されることになるので注意が必要です。
S3_BUCKET = "..."
S3_REGION = "..."
S3_ACCESS_KEY_ID = "..."
S3_SECRET_ACCESS_KEY = "..."
shrine.rb
の編集
あとは設定ファイルをS3にダイレクトアップロード仕様に編集すれば完了です。
require 'shrine/storage/s3'
という箇所とif Rails.env.production?〜
の部分を追記していきます。
require "shrine"
require 'shrine/storage/file_system'
require 'shrine/storage/s3' # ここを追記
# アップロードするディレクトリの指定
# ここを追記
if Rails.env.production?
s3_options = {
access_key_id: ENV['S3_ACCESS_KEY_ID'],
secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
region: ENV['S3_REGION'],
bucket: ENV['S3_BUCKET']}
Shrine.storages = {
cache: Shrine::Storage::S3.new(prefix: 'cache', **s3_options),
store: Shrine::Storage::S3.new(prefix: 'store', **s3_options)}
else
# この部分は元々記述していた
Shrine.storages = {
cache: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'),
store: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/store')}
end
# 使用するプラグインの宣言
Shrine.plugin :activerecord
Shrine.plugin :cached_attachment_data
Railsサーバーの再起動
最後にRailsサーバーを再起動させてあげれば完了です!お疲れ様でした!
これでS3の方に画像データが保存されるようになっているはずです。
まとめ
- Shrineの使い方をまとめた。
- ShrineはCarrierWaveよりもシンプルであり、必要に応じてプラグイン追加によって機能の拡張が可能である。
- Railsにて画像のリサイズやAWS S3へのダイレクトアップロードができるようにする設定を紹介した。