近年は技術が急速に発達し、個人でもAWSを利用する機会が増えてきました。個人開発者の方でも、アプリケーションのスケールに合わせてAWSを使用するかどうか検討する人もいれば、技術力を上げる為に試しに使ってみるという人もいるでしょう。
僕もAWS S3を使用した画像アップロード機能については苦労した記憶があるのでこの記事で解説していこうと思います。
S3 + CarrierWave + Fogを使った画像アップロード機能を解説した記事は多数ですが、どうにも情報がバラけているものが多く、体系的でないため需要があると踏み、記事作成に至りました。
ゼロベースで解説していきますので、是非一緒に手を動かしてみて下さい。(但し、AWSのアカウントは作成済みであることとします。)
目次
環境
ruby 2.5.1p57
Rails 5.2.2
ベースとなるアプリケーションを作成する
まずはベースとなるアプリケーションをRailsで作成します。
$ rails new ImageUploadApp
アプリケーションを作ったら bundle install –path vendor/bundle を実行します。
rails s でサーバーが立ち上がることを確認したら次のステップへ進みましょう。
※Error loading the ‘sqlite3’ Active Record adapter. Missing a gem it depends on? can’t activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (LoadError) の様なエラーが出る場合、sqlite3のバージョンによってエラーが出ているため、gem ‘sqlite3’, ‘~> 1.3.6’ とバージョンを指定して下さい。
画像アップロード対象のモデルを作成する
次に画像アップロード対象のカラムを持つモデルの作成を行います。
$ rails g model image name:string image:string
画像の名前を格納するnameカラムと画像保存用のimageカラムをもったImageモデルを作成するコマンドです。
すると、この様にマイグレーションファイルが作成されます。
class CreateImages < ActiveRecord::Migration[5.2] def change create_table :images do |t| t.string :name t.string :image t.timestamps end end end
内容を確認してから、$ rails db:migrate します。
対応したコントローラを作成する
次に、このImageモデルに対応したコントローラを作成します。次のコマンドを打って下さい。
$ rails g controller images index new edit show
コントローラ作成時にアクション名も同時に記述すると、アクションの定義と同時にViewファイルも同時に作成されるので手間が省けます。
ここで、$ rails s を実行して
localhost:3000/images/index
localhost:3000/images/new
localhost:3000/images/show
localhost:3000/images/edit
にアクセスして実際にページが表示されることを確認しておきましょう。
ルーティングを修正する
ルーティングとビューファイルの修正をしておきます。現在はローカルホストを開くとルートでRailsのページが出てきます。これを修正します。具体的にはルートに作成したビューファイルを対応つけて、そのページを起点にどのページにも遷移できるようにします。
まずはルーティングの設定を少し弄ってみます。routes.rb を次の様に修正してみて下さい。
Rails.application.routes.draw do root to: 'images#index' resources :images, except: :index end
すると、URIパターンが以下の様に変わります。
Prefix Verb URI Pattern Controller#Action
root GET / images#index
images POST /images(.:format) images#create
new_image GET /images/new(.:format) images#new
edit_image GET /images/:id/edit(.:format) images#edit
image GET /images/:id(.:format) images#show
PATCH /images/:id(.:format) images#update
PUT /images/:id(.:format) images#update
DELETE /images/:id(.:format) images#destroy
rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
ルートが修正されたので、$ rails s でindexビューファイルにアクセスされることを確認して下さい。
コントローラ内の記述を埋めていく
さて、コントローラもビューファイルも既に作成されており、ルーティングの設定も終わったので次にコントローラ内の記述を埋めていきます。ほぼテンプレートなのでコピペでも大丈夫です。
class ImagesController < ApplicationController def index @images = Image.all end def new @image = Image.new end def create @image = Image.new(image_params) if @image.save redirect_to root_url else render :new end end def edit @image = Image.find(params[:id]) end def update @image = Image.find(params[:id]) @image.update_attributes(image_params) if @image.save redirect_to image_url(@image) else render :edit end end def show @image = Image.find(params[:id]) end def destroy @image = Image.find(params[:id]) @image.destroy redirect_to root_url end private def image_params params.require(:image).permit(:name, :image) end end
ビューファイルを修正する
コントローラの記述が大まかに記述できたので、次にビューファイルを修正していきます。とりあえず名前のカラムだけを使用した簡単なCRUD機能を使用できる様に設定します。
<h1>Images一覧</h1> <div> <%= link_to "新規作成", new_image_path %> </div> <% @images.each do |image| %> <h2><%= image.name %></h2> <%= link_to "詳細", image_path(image) %> <%= link_to "削除", image_path(image), method: :delete %> <% end %>
<h1>Image詳細</h1> <h2><%= @image.name %></h2> <p><%= link_to "編集", edit_image_path(@image) %></p> <p><%= link_to "戻る", root_path %></p>
<h1>Image作成</h1> <%= form_with model: @image, local: true do |form| %> <%= form.label :name %> <%= form.text_field :name %> <p><%= form.submit %></p> <% end %>
<h1>Image編集</h1> <%= form_with model: @image, local: true do |form| %> <%= form.label :name %> <%= form.text_field :name %> <p><%= form.submit %></p> <% end %>
意味がわかる方はコピペでOKです。実際にサーバーを起動して動作するか確認して下さい。
ルートページがこの様に動作していればOKです。では、本題の画像アップロード機能を作成していきましょう。
画像アップロード機能を作成する
前置きが長くなりましたがここからS3を利用した画像アップロード機能を作成していきます。
AWS側の設定
AWSのアカウントを作成しているという前提で、必要な設定をしていきます。
IAMユーザの作成
AWSには権限をそれぞれで変更したり、操作を制限するための個別アカウントの様なものを設定できます。それがI AMというサービスです。それでは、AWSコンソールを開いてI AMのページを開きましょう。
IAMユーザを作成します
IAMユーザを作成するときの注意点ですが、Railsアプリケーションからアクセスするためにアクセスキーとシークレットキーによるアクセスを許可する必要があります。よって以下のチェックボックスに必ずチェックを入れましょう。名前は適当でOK。
また、セキュリティの関係上、AWSマネジメントコンソールへのアクセスもIAMユーザーごとにします。「AWSマネジメントコンソールへのアクセス」にチェックを入れてパスワードを設定します。IAMユーザー作成後にログインリンク情報が取得できるので次回からはそこからアクセスして下さい。
次のステップでは、アクセス権限の設定をします。このI AMユーザでS3を操作するため、S3へのフルアクセスを可能にしておきます。(ここら辺の設定は様々な組み合わせがあるので、慣れてきたら自分で変えてみるのも良いでしょう。)
タグの設定は任意です。
内容を確認してOKであればユーザーを作成します。
S3バケットを作成する
IAMユーザーが作成できたら、画像を保存するサーバーであるS3のバケットの作成を行います。IAMユーザー作成時と同様に「S3」とサービスを検索するとすぐにアクセスできます。
S3のバケット名を設定して、後は推奨通り設定し、バケットを作成します。
これでAWS側の設定は以上です。次はgemの設定をしていきましょう!
スポンサードリンク
CarrierWaveを使った画像アップロード機能実装
AWSの設定が終わったので後はRails側の設定です!後一息なので頑張っていきましょう。以下の通りに作業しても良いですが、公式ドキュメントの情報が最も信頼できるので、基本的にはこちらを読み進めつつ作業することを原則とします。
gemのインストール
Gemfileに指定のgemを記述します。
gem 'carrierwave' gem 'fog-aws'
一部Qiitaやブログ等では、gem ‘fog’ を利用しているものがありますが、現在は gem ‘aws-fog’ というgemが公式で推奨されているのでこちらを使用します。$ bundle install でgemをインストールしましょう。
アップローダを作成する
CarrireWaveのUploaderと呼ばれるものを作成します。gemが導入できていれば以下のコマンドで作成可能です。
$ rails g uploader Image
作成されたアップローダを以下の様に変更します。
class ImageUploader < CarrierWave::Uploader::Base if Rails.env.development? storage :file elsif Rails.env.test? storage :file else storage :fog end def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end def extension_whitelist %w(jpg jpeg gif png) end def filename "#{secure_token}.#{file.extension}" if original_filename.present? end protected def secure_token var = :"@#{mounted_as}_secure_token" model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid) end end
2~8行目では、環境の違いによる画像の保存先の設定をしています。この場合だとtestまたはdevelopment環境では/public配下に、production環境だとfogすなわちS3に保存されます。
モデルとアップローダを紐づける
対象のモデルに以下の一行を追加し、アップローダとモデルを紐づけます。
class Image < ApplicationRecord mount_uploader :image, ImageUploader end
CarrierWaveの設定
CarrierWaveの設定を行います。config/initializers/carrierwave.rb というファイルを自分で作って、以下の設定を記述しましょう。
CarrierWave.configure do |config| if Rails.env.production? config.storage :fog config.fog_provider = 'fog/aws' config.fog_directory = '設定したバケット名' config.fog_credentials = { provider: 'AWS', aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], region: ENV['AWS_REGION'], path_style: true } else config.storage :file config.enable_processing = false if Rails.env.test? end end CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
この設定は、本番環境であればAWS S3を利用し、開発及びテスト環境ではローカルに画像を保存する設定です。とりあえずはこれで設定しておきます。
バケット名と書かれている部分は、先ほど作成したバケット名を、環境変数にはそれぞれ各自のものを .env に設定して下さい。環境変数にアクセスできるように、gem ‘dotenv-rails’ を導入して $ bundle install としておきます。
実はこれで基本的な設定は終了です。
アップロード機能が実装されているか確かめる
取り急ぎ、「アップロード機能」が動くかどうかの確認をします。(まだこのままではS3にアップロードされる様に設定されていないので、ローカルに画像が保存されるかどうかを確認します。)
ビューファイルの修正。
<h1>Image作成</h1> <%= form_with model: @image, local: true do |form| %> <%= form.label :name %> <%= form.text_field :name %> <%= form.label :image %> <%= form.file_field :image %> <p><%= form.submit %></p> <% end %>
「ファイルを選択」から、画像をアップロードしてみましょう。
画像が、設定された階層と、設定したファイル名できちんと保存されていることを確認しました。
画像の表示及び細かい部分を修正する
最終調整として、アップロードされた画像が表示されるように設定します。そのままだと画像のサイズによってデザイン崩れが起きたりするので、CSSも少し修正します。
<h1>Image編集</h1> <%= form_with model: @image, local: true do |form| %> <%= form.label :name %> <%= form.text_field :name %> <%= form.label :image %> <%= form.file_field :image %> <p><%= form.submit %></p> <% end %>
詳細ページの画像にクラスを付与します。
<h1>Image詳細</h1> <h2><%= @image.name %></h2> <div><%= image_tag @image.image.to_s, class:"image" %></div> <p><%= link_to "編集", edit_image_path(@image) %></p> <p><%= link_to "戻る", root_path %></p>
CSS。簡易のためapplication.cssに記述します。
/* *= require_tree . *= require_self */ .image { max-width: 50%; height: auto; }
これで準備が整いました。最終段階です。
画像がローカルからS3にアップロードされる様に変更する
今のままだと画像がローカルに保存されてしまうので、開発環境でもS3にアップロードできる様に変更します。
アップローダでは環境によってアップロード先を分けるように設定しているので、まずはそこを全てS3にするように設定します。
# if Rails.env.development? # storage :fog # elsif Rails.env.test? # storage :fog # else # storage :fog #↓次の様に変更 strage :fog
次に、CarrierWaveの設定を行います。上で設定したものは少し煩雑なので、必要な記述だけするように変更を加えます。
CarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], region: ENV['AWS_REGION'], } config.fog_directory = 'バケット名' end CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
config.providor = ‘fog’を記述すると、アップローダと同様の記述をしてしまうためエラーが出るという報告を見かけたことがあるため、記述を取り除いています。
コードの修正はたったこれだけです。次に、AWS側の設定を行います。
作成したS3バケット>アクセス権限>CORSの設定から、ローカルホストからでも許可及びHTTPメソッドの許可をするようにCORS設定を加えます。
<CORSConfiguration> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
では、画像をアップロードしてみましょう。
きちんと表示されました!
最後に、本当にS3にアップロードされているか確認します。
できてます!これでローカルからでもS3へアップロードができることが確認できました。
※Access Denined(Excon::Errors::Forbidden (Expected(200) <=> Actual(403 Forbidden))というエラーが出る方
AWS側でアクセス権限周りで不都合が出ている時に生じるエラーです。もう一度
・IAM権限設定(ポリシーの付与)
・アクセスキー に間違いはないか
・S3バケットのアクセス設定は間違えてないか
を確認して下さい。特に僕の場合はS3バケットの設定がミスしていて、丸2日時間を費やしたという経験があります。気をつけて下さい。
おわりに
今回は実際にRailsアプリケーションを作成しながら画像アップロード機能の実装について手を動かしながら学習していきました。少なからずCarrierWaveへの理解は深まったはずです。
コードを少しづついじってみて、オリジナルのアプリケーションへ是非転用してみて下さいね!
スポンサードリンク
わかりやすかったです。ありがとうございます。
杉山様
記事をご覧いただきありがとうございます!自分も当時非常に苦労した覚えがあったので,お役に立てればこの記事を書いた甲斐があります!