Active Storageで複数画像をCloudinary に上げ Herokuに公開する
初めに
現場で使える Ruby on Rails 5速習実践ガイド では、ファイルをアップロードしてモデルに添付する方法として、Active Storage が紹介されています。
ローカル環境で、一枚の画像を添付する方法について書かれておりました。
せっかくですので、
- 複数の画像の添付方法
- 画像や動画の管理が得意なクラウドサービス Cloudinary の利用方法
- Heroku への 公開方法
について、記していきます。
ついでに、SendGridを使ってメール送信できるようにします。
Active Storage とは
Rails 5.2 から、ActiveStorage が同梱されました。
クラウドストレージサービス(Amazon S3 や Cloudinaryなど)への画像・動画をアップロードして、データベース(ActiveRecord)に紐付けることが簡単にできるようになりました。
Cloudinary とは
Cloudinaryとは、画像や動画の配信や編集ができるクラウドサービスです。
無料でも、一ヶ月当たり25クレジット(≒25GB)まで使うことができますので、小規模な開発には充分かと思います。
準備
Active Storage は、Rails アプリを新規作成した際に導入されています。
そして、以下のGemも導入しておきます。
# Gemfile
gem 'image_processing' # (サイズ変更など)画像処理用
gem 'cloudinary', require: true # Cloudinary
gem 'activestorage-cloudinary-service' # Cloudinary と Active Storage の連携をする
gem 'active_storage_validations' # 画像ファイルのバリデーション用
% bundle install
Active Storage をインストールします。
% rails active_storage:install
マイグレーションファイルが生成されますので、データベースに反映させるべく、migrate コマンドを実行します。
% rails db:migrate
添付ファイルの実体を、どこに保存するのか、設定を行います。
開発環境ではローカルに、本番環境では cloudinary に、添付ファイルが保存されるように設定します。
# config/development.rb
Rails.application.configure do
# Store uploaded files on the local file system (see config/storage.yml for options).
# アップロードされたファイルをローカルファイルシステムに保存します
# (オプションについては config/storage.yml を参照してください)。
config.active_storage.service = :local
end
# config/production.rb
Rails.application.configure do
config.active_storage.service = :cloudinary
end
:local, :cloudinary は、config/storage.yml に詳細を記述します。
# config/storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
cloudinary:
service: Cloudinary
cloud_name: <%= Rails.application.credentials.dig(:cloudinary, :cloud_name) %>
api_key: <%= Rails.application.credentials.dig(:cloudinary, :api_key) %>
api_secret: <%= Rails.application.credentials.dig(:cloudinary, :api_secret) %>
後々、ビューで画像を表示するのに便利なので、以下も記述しておきます。
enhance_image_tag: true と書くことで、
ビュー内で、= image_tag と書いた際に、cloudinary による便利な機能拡張が使えるようになります。
# config/cloudinary.yml
development:
cloud_name: <%= Rails.application.credentials.dig(:cloudinary, :cloud_name) %>
api_key: <%= Rails.application.credentials.dig(:cloudinary, :api_key) %>
api_secret: <%= Rails.application.credentials.dig(:cloudinary, :api_secret) %>
enhance_image_tag: true
static_file_support: false
production:
cloud_name: <%= Rails.application.credentials.dig(:cloudinary, :cloud_name) %>
api_key: <%= Rails.application.credentials.dig(:cloudinary, :api_key) %>
api_secret: <%= Rails.application.credentials.dig(:cloudinary, :api_secret) %>
enhance_image_tag: true
static_file_support: false
test:
cloud_name: <%= Rails.application.credentials.dig(:cloudinary, :cloud_name) %>
api_key: <%= Rails.application.credentials.dig(:cloudinary, :api_key) %>
api_secret: <%= Rails.application.credentials.dig(:cloudinary, :api_secret) %>
enhance_image_tag: true
static_file_support: false
ここで、書かれている Rails.application.credentials は、鍵の管理を行うために、Rails 5.2 から登場したcredentials(信任状)という機能です。
大切な鍵の情報は、config/credentials_yml.enc に暗号化されて保存されています。
次のコマンドで、暗号化された情報を見ることができます。
% rails credentials:show
編輯するためには、次のコマンドを実行します。
% rails credentials:edit
エディタが立ち上がりますので、適宜編輯します。
Command + S で保存して、Command + W でタブを閉じます。
cloudinary:
cloud_name: (cloudinaryに付けた任意の名前)
api_key: (cloudinaryより指定された15桁の数字)
api_secret: (cloudinaryより指定された27桁の英数記号)
# Used as the base secret for all MessageVerifiers in Rails,
including the one protecting cookies.
secret_key_base:
(128桁の16進数)
タスクモデルに画像を添付できるようにする。
Taskモデルに画像ファイルを添付できるようにします。
ついでに、activestorage-validator による添付画像の検証も追加します。
# app/models/task.rb
class Task < ApplicationRecord
# has_one_attached :image # 添付画像は一つ
has_many_attached :images # 複数の添付画像
# activestorage-validator による添付画像の検証
validates :images,
content_type: %i(gif png jpg jpeg), # 画像の種類
size: { less_than_or_equal_to: 5.megabytes }, # ファイルサイズ
dimension: { width: { max: 2000 }, height: { max: 2000 } } # 画像の大きさ
end
ビューも作成します。
# app/views/task/new.html.slim
h1 タスクの新規登録
= link_to '一覧', tasks_path, class: 'ui right floated primary tertiary button'
= render partial: 'form', locals: { task: @task }
# app/views/task/_form.html.slim
= form_with model: task, class: 'ui form', local: true do |f|
.field
= f.label :name
= f.text_field :name, required: true
.field
= f.label :description
= f.text_area :description
.field
= f.label :images
- if task.images.attached?
- task.images.each do |image|
= image_tag image
/ = image_tag task.image.variant(resize_to_limit: [300, 300])
= f.check_box :image_ids, { multiple: true }, image.id, false
= f.label "image_ids_#{image.id}"
| 画像を削除する
= f.file_field :images,
accept: 'image/jpg, image/jpeg, image/png, image/gif',
multiple: true
= f.submit nil, class: 'ui primary button'
画像の表示を担当しているのは、次の部分です。
画像の添付があった場合、eachメソッドで、全ての画像を表示させています。
- if task.images.attached?
- task.images.each do |image|
= image_tag image
また、不要な画像を削除できるよう、チェックボックスを設けています。
= f.check_box :image_ids, { multiple: true }, image.id, false
= f.label "image_ids_#{image.id}"
| 画像を削除する
添付ファイルを複数選択できるようにするとともに、
画像ファイルのみを選べるようにしています。
= f.file_field :images,
accept: 'image/jpg, image/jpeg, image/png, image/gif',
multiple: true
コントローラを作成します。
# app/controllers/tasks_controller.rb
class TasksController < ApplicationController
def update
# 画像の削除処理
params[:task][:image_ids]&.each do |image_id|
@task.images.find(image_id).purge
end
if @task.update(task_params)
redirect_to tasks_url, notice: "タスク「#{@task.name}」を更新しました。"
else
render :edit
end
end
private
def task_params
# 一つの画像を添付する場合
# params.require(:task).permit(:name, :description, :image)
# 複数の画像を添付する場合
params.require(:task).permit(:name, :description, images:[])
end
end
update アクションで、チェックが入っている画像を削除できるようにしています。
また、フォームから添付ファイルを受け取れるよう、task_paramsに、imageを追加しています。
以上で、Active Storage を使った複数画像の添付は完成です。
少し、ビューが味気ないので、
Fomantic-UI の card を使って画像を表示することとし、
css/javascript を使って、ファイルフォームを綺麗にすると、次のようになります。
# app/views/task/_form.html.slim
- if task.errors.present?
ul#error_explanation
- task.errors.full_messages.each do |message|
li = message
= form_with model: task, class: 'ui form', local: true do |f|
.field
= f.label :name
= f.text_field :name, required: true
.field
= f.label :description
= f.text_area :description
.field
= f.label :images
- if task.images.attached?
.ui.cards
- task.images.each do |image|
.card
.image
= image_tag image
/
/ = image_tag task.image.variant(resize_to_limit: [300, 300])
.extra.content
.ui.checkbox
= f.check_box :image_ids, { multiple: true }, image.id, false
= f.label "image_ids_#{image.id}"
| 画像を削除する
= f.file_field :images,
accept: 'image/jpg, image/jpeg, image/png, image/gif',
multiple: true,
id: 'embed_file_input'
.ui.fluid.action.input.mb-3
input#selected_filenames_display_area disabled="disabled"
placeholder="画像ファイルはありません" type="text"
label.ui.small.teal.left.floated.button for="embed_file_input"
= semantic_icon('upload')
| 画像選択
= f.submit nil, class: 'ui primary button'
css:
input[type="file"] {
display: none;
}
#selected_filenames_display_area {
opacity: 1;
}
javascript:
// "embed_file_input" という ID属性の要素を取得する。(画像選択ボタン)
const input_files = document.getElementById("embed_file_input");
// 選択ファイル名表示領域
const selected_filenames_display_area =
document.getElementById("selected_filenames_display_area");
// 値が変化した時(ファイル選択時)に実行されるイベント
input_files.onchange = function() {
// FileList オブジェクトを取得する
let file_lists = input_files.files;
// 画像ファイル名格納用の配列
let file_names = []
for (let i = 0; i < file_lists.length; i++) {
// File オブジェクトより、画像ファイル名を取得する
file_names.push(file_lists[i].name)
}
// 選択ファイル名表示領域に、画像ファイル名を書き出す
selected_filenames_display_area.value = file_names.join(', ')
}
本番環境でSendGridを使う
production.rb に SendGrid を使ってメール送信できるよう、追記します。
# config/environments/production.rb
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for
# immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.delivery_method = :smtp
host = 'taskleaf.herokuapp.com'
config.action_mailer.default_url_options = { host: host }
ActionMailer::Base.smtp_settings = {
address: 'smtp.sendgrid.net',
port: '587',
authentication: :plain,
user_name: ENV['SENDGRID_USERNAME'],
password: ENV['SENDGRID_PASSWORD'],
domain: 'heroku.com',
enable_starttls_auto: true
}
Heroku への公開
アカウントの作成
Heroku(へろく)は、Ruby on Rails で作成したウェブアプリを簡単に公開(デプロイ)できるサービスです。
https://heroku.com にアクセスし、Sign up から、自分のアカウントを作成します。
CLI(Command Line Interface)のインストール
GUI(Graphical User Interface)を使って、ブラウザからウェブアプリの作成もできます。
そしてせっかくですから、Heroku Command Line Interface (CLI) もインストールしておきます。
ターミナルからコマンド一つで、いろいろできるようになるので、慣れると簡単です。
% brew tap heroku/brew && brew install heroku
ウェブアプリの作成とGitを使っての公開(デプロイ)
% cd taskleaf
% git init
Initialized empty Git repository in .git/
% git add .
% git commit -m "My first commit"
Herokuにログインしてウェブアプリを作成します。
名前は、taskleaf にします。(すでに使われていたら別の名前にします。)
% heroku login
% heroku create taskleaf
Heroku では、いろいろなadd-on(追加機能)を使えるようになっています。
データベースには、Postgresql を、
ストレージサービスには、Cloudinary を、
メール送信サービスとして、SendGrid を使いたいので、
以下のコマンドで、機能追加します。
各アドオンとも、利用する容量等によって、さまざまな料金プランが用意されていますが、
ここでは、無料プランにしています。
% heroku addons:create heroku-postgresql:hobby-dev
% heroku addons:create cloudinary:starter
% heroku addons:create sendgrid:starter
% heroku config:get SENDGRID_USERNAME
% heroku config:get SENDGRID_PASSWORD
<font color="red">
従前は、
% heroku addons:create sendgrid:starter
% heroku config:get SENDGRID_USERNAME
% heroku config:get SENDGRID_PASSWORD
と書くことで、動作していましたが、
SendGridの仕様が変更となり、USERNAMEとPASSWORDによる認証ではなく、
APIキーによる認証を求められるようになりました。
手順を以下の記事に記しましたので、ご覧ください。
Heroku にデプロイした Rails アプリから SENDGRID を使ってメール送信する
</font>
公開(デプロイ)します。
% git push heroku master
公開できましたので、データベースを更新します。
% heroku run rails db:migrate
ブラウザで開いて確認します。
% heroku open
ローカル環境と同じようにファイルをアップロードできるはずです。
また、Cloudinaryのサイト(https://cloudinary.com/)にログインすると、
「Media Library」にアップロードした画像ファイルがあることが確認できるはずです。
あとがき
ざっくりと書きましたが、どなたかのお役に立てば幸いです。
参考
【Rails 5.2】 Active Storageの使い方 - Qiita
【Rails on Docker on Heroku】ActiveStorage + Cloudinaryで画像を管理するメモ
Discussion