Dragon Arrow written by Tatsuya Nakaji, all rights reserved animated-dragon-image-0164

rails5.2 Active Storage

updated on 2019-07-11

イメージ

Active Storageの導入

今までは画像やファイルを保存したいときは、paperclipを使っていましたが、rails5.2からはactive storageが推奨されている様です。

そして腰抜かすくらい簡単に

導入できてしまいます  


早速やっていきます!!


1. active_storageのインストール

$ rails active_storage:install
$ rails db:migrate

active_storageの設定ファイルをインストールした後、マイグレーションを実行することでactive_storage_blobsactive_storage_attachmentsという名前のテーブルが2つ生成されます。

Blobはファイル名、ファイルの種類、バイト数、誤り検出符号などのメタデータを保持するモデルで、Attachmentモデルは、BlobオブジェクトとActive Recordオブジェクトを紐付けるための中

テーブルですが、全くいじる必要のないモデルなので、特に覚えなくても結構です。


2. model生成

$ rails g model Article title:string body: text
$ rails db:migrate


3. 実装


1つの添付ファイルの場合


article.rb

# article.rb
class Article < ApplicationRecord
  has_one_attached :image 
end

:imageはファイルの呼び名で、:photo:avatar:profileなど、好きなものを指定してください。

ここで指定した image は、articlesテーブルのカラムの様な扱いになりますが、imageテーブルなどを作る必要はないです。Active Storageは裏側でBlobAttachmentモデルを使って、勝手に

article.imageを使えるようにしてくれます。


articles_controller.rb

class CommentsController < ApplicationController
  before_action :find_article, only: [:edit, :update, :show, :destroy]

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      flash[:notice] = "保存しました"
      redirect_to article_path(@article)
    else
      flash[:alert] = "エラーが発生しました"
      render :new
    end
  end

  def show
  end

  def edit
  end

  def update
    if @article.update(article_params)
      flash[:notice] = "更新しました"
      redirect_to article_path(@article)
    else
      flash[:alert] = "エラーが発生しました"
      render :edit
    end
  end

  def destroy
    if @article.destroy
      flash[:notice] = "削除しました"
      redirect_to articles_path
    else
      flash[:alert] = "エラーが発生しました"
    end
  end

  # imageは、titleフィールドやbodyフィールドと同じ様に、articlesテーブルのカラムの様な扱いをする
  private
  def article_params
    params.require(:article).permit(:title, :body, :image)
  end

  def find_article
    @article = Article.find(params[:id])
  end

end


articles/_form.html.erb

<%= form_with model: article, local: true  do |f| %>
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2><%= "#{pluralize(@article.errors.count, "error")} により保存できませんでした" %></h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <%= f.text_area :title %><br>
  <%= f.file_field :image %><br>
  <%= f.text_area :body %><br>
  <%= f.submit %>
<% end %>


new.html.erb

<h2>新規投稿</h2>
<%= render "articles/form", artilce:@article %>


edit.html.erb

<h2>編集</h2>
<%= render "articles/form", artilce:@article %>


show.html.erb

<div class="col-sm-11 col-sm-offset-1 col-xs-12">
<h2 class="text-center"><%= @article.title %></h2>
<div class="text-center">
<% if @article.image.attached? %>
<%= image_tag @article.image, :alt => "イメージ", width: '30%', height: '30%' %>
<% end %>
</div>
<h5 class="text-center"><%= @article.created_at.strftime('%b %d, %Y') %></h5>
<div class="fr-view">
  <%= raw @article.body %>
</div>
</div>

<% if @article.image.attached? %>
<%= image_tag @article.image
, :alt => "イメージ", width: '30%', height: '30%' %>
<% end %>

の部分の :alt => "イメージ", width: '30%', height: '30%'は好きに変えてください。

これでactive_storageへの投稿、更新、削除が完成しました  


しかし...active_storageは複数のファイルを保存することもできますので、そちらも紹介します。


複数の添付ファイルの場合

article.rb

# article.rb
class Article < ApplicationRecord
  # has_one_attached :image
  has_many_attached :images
end


articles_controller.rb

class CommentsController < ApplicationController
  before_action :find_article, only: [:edit, :update, :show, :destroy]

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      flash[:notice] = "保存しました"
      redirect_to article_path(@article)
    else
      flash[:alert] = "エラーが発生しました"
      render :new
    end
  end

  def show
  end

  def edit
  end

  def update
    if @article.update(article_params)
      flash[:notice] = "更新しました"
      redirect_to article_path(@article)
    else
      flash[:alert] = "エラーが発生しました"
      render :edit
    end
  end

  def destroy
    if @article.destroy
      flash[:notice] = "削除しました"
      redirect_to articles_path
    else
      flash[:alert] = "エラーが発生しました"
    end
  end

  # imageは、titleフィールドやbodyフィールドと同じ様に、articlesテーブルのカラムの様な扱いをする
  private
  def article_params
    # params.require(:article).permit(:title, :body, :image)
    params.require(:article).permit(:title, :body, :images)
  end

  def find_article
    @article = Article.find(params[:id])
  end

end


articles/_form.html.erb

<%= form_with model: article, local: true  do |f| %>
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2><%= "#{pluralize(@article.errors.count, "error")} により保存できませんでした" %></h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <%= f.text_area :title %><br>
  <!-- <%= f.file_field :image %><br> -->
  <%= f.file_field :images, multiple: true %><br>
  <%= f.text_area :body %><br>
  <%= f.submit %>
<% end %>


show.html.erb

<div class="col-sm-11 col-sm-offset-1 col-xs-12">
<h2 class="text-center"><%= @article.title %></h2>
<div class="text-center">
<!-- <% if @article.image.attached? %>
<%= image_tag @article.image, :alt => "イメージ", width: '30%', height: '30%' %>
<% end %> -->
<% if @article.images.attached? %>
  <% @article.images.each do |image| %>
    <%= image_tag image %> <br>
  <% end %>
<% end %>

</div>
<h5 class="text-center"><%= @article.created_at.strftime('%b %d, %Y') %></h5>
<div class="fr-view">
  <%= raw @article.body %>
</div>
</div>


new.html.erb(変更なし)

<h2>新規投稿</h2>
<%= render "articles/form", artilce:@article %>


edit.html.erb(変更なし)

<h2>編集</h2>
<%= render "articles/form", artilce:@article %>


THAT'S ALL RIGHT!!



ちなみに、今回は使わなかったものの、urlを生成したりプレビュー、ダウンロードリンクをするときは以下を使用

  • <%= link_to 'プレビュー', rails_blob_path(article.image, disposition: 'preview') %>
  • <%= link_to 'ダウンロード', rails_blob_path(article.image, disposition: "preview") %>
  • view以外でコンソールとかでrails_blob_pathを使うときは
  • Rails.application.routes.url_helpers.rails_blob_path で使う
  • 例) Rails.application.routes.url_helpers.url_for(Article.find(145).image) * url_for も rails_blob_path と同じ!


4.ファイルの保存先の変更


config/environments/development.rb

  # ...

  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :local

  # ...

config/environments/production.rb

  # ...

  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :local

  # ...

defaultの local とは、 config/storage.yml で定義された保存先の名前

config.active_storage.service = :local:amazon, :google, :microsoft のいづれかと置き換えて、config/storage.ymlの方に、必要な認証情報などの値を入力します。


config/storage.yml

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket

# Remember not to checkin your GCS keyfile to a repository
# google:
#   service: GCS
#   project: your_project
#   credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
#   bucket: your_own_bucket

# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
#   service: AzureStorage
#   storage_account_name: your_account_name
#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
#   container: your_container_name


defaultの保存先の local は、使用するサービスが Disk (ローカルディスク)に設定れていて、保存先がrailsアプリ直下の/storageディレクトリに指定されています。

保存先に応じてコメントを解除してください。


amazon: を使用するあなたは gem aws-sdk-s3

goolge: を使用するあなたは gem google-cloud-storage

microsoft: を使用するあなたは gem azure-storage をGemfileに追記


なお、アクセスキーなどの機密情報は、Rails Credentials(暗号化して保存するためのRails新機能)を使って入力!!

$ EDITOR=vim rails credentials:edit

i を押す

実際の値を入れていく

escを押す

ZZ を押す

保存完了

(入力した内容はconfig/master.keyを用いて暗号化され、config/credentials.yml.encが生成されます)

$ rails credentials:show で確認できる


Amazon S3 を使う場合

gem "aws-sdk-s3", require: false

$ bundle installと叩きます。


config/environments/production.rb

# ファイル保存先を:localから:amazonに変更

config.active_storage.service = :amazon

config/environments/development.rb

# 開発環境(development)でAmazon S3の動作を確認したい場合はこちらの方も変更しましょう

config.active_storage.service = :amazon

config/storage.yml

# 以下の部分をコメント解除する
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: ap-northeast-1 # これは日本の地域コード
  bucket: my_bucket_name # 自分で作成したS3のバケットの名前
$ EDITOR=vim rails credentials:edit 
(エディタはVim(vim)かAtom(atom)かVSCode(code)かSublimeText(subl))
aws:
 access_key_id: 123 #ここに自分のアクセスキーIDをコピペ
 secret_access_key: 456 #ここに自分のシークレットアクセスキーをコピペ

 Vim: iで入力開始、escZZで終了


$ rails credentials:show
で内容を確認できる


以上でs3にアップロードされるようになりました。s3がこんなに簡単に使えてしまうことが驚き栗の木超ラッキーです!


Credentialsを使用する場合

config/credentials.yml.encの中身を復号化するために必要なconfig/master.keyファイルを本番環境にも配置する必要があります。しかし、master.keyはセキュリティ上,  .gitignore されてあり、Gitリポジトリをそのままデプロイする本番環境(Herokuなど)の場合は、master.key の中身をコピーして、環境変数 RAILS_MASTER_KEY としてセットしなければいけません。ちなみに、Herokuをお使いの場合は $ heroku config:set RAILS_MASTER_KEY=123で環境変数を用意できます。


参考文献 こちらの方