【Rails5】AWS S3+CarrierWave+Fog::AWSを利用して画像アップロード機能を作成する

近年は技術が急速に発達し、個人でも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モデルを作成するコマンドです。

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日時間を費やしたという経験があります。気をつけて下さい。

アクセス権限の項目がtrueになっている人は、一度全てfalseにしてみるというのも1つの手段。AWSに詳しくないと詰まってしまう要素の1つ。

おわりに

今回は実際にRailsアプリケーションを作成しながら画像アップロード機能の実装について手を動かしながら学習していきました。少なからずCarrierWaveへの理解は深まったはずです。

コードを少しづついじってみて、オリジナルのアプリケーションへ是非転用してみて下さいね!

スポンサードリンク

2 件のコメント

    • 杉山様
      記事をご覧いただきありがとうございます!自分も当時非常に苦労した覚えがあったので,お役に立てればこの記事を書いた甲斐があります!

  • コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です