【超かんたん】Active Storageで画像投稿機能を実装しよう！

Active Storageを利用して画像投稿機能を実装します。
今回も初心者向けにレシピ投稿アプリを例に作成していきます。

#####完成イメージ
![teri.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/8b20312a-b872-ee93-3806-10204ad4e267.jpeg)

##Active Storageとは
ファイルアップロード機能を簡単に実装できるGem。[Railsガイド](https://railsguides.jp/active_storage_overview.html)

https://railsguides.jp/active_storage_overview.html

##事前準備
####ImageMagickの導入
**ImageMagickとは**
> ImageMagickとは画像処理ライブラリです。
コマンドラインから画像に処理を加えることができるツールです。
Homebrewからインストールするので一度構築すれば次回以降この作業は不要になります。

```:ターミナル
brew install imagemagick
```

####必要なGemのインストール
MiniMagickとImageProcessingをインストールします。

**MiniMagickとは**
>mageMagickの機能をRubyで扱えるようにするためのGem。[公式ドキュメント](https://github.com/minimagick/minimagick)

**ImageProcessingとは**
>画像サイズを調整するためのGem。[公式ドキュメント](https://github.com/janko/image_processing)

```ruby:Gemfile
gem 'mini_magick'
gem 'image_processing'
```

```:ターミナル
bundle install
```

##Active Storageの導入
Active Storageをアプリケーション内で使用するために以下のコマンドを実行します。

```:ターミナル
rails active_storage:install
```
インストールが完了するとActive Storageに関連したマイグレーションが作成されるので、マイグレートします。

```:ターミナル
rails db:migrate
```
マイグレートが完了したら**active_storage_blobs**テーブルと**active_storage_attachments**テーブルが作成されているかスキーマファイルを確認しましょう。[Railsガイド](https://railsguides.jp/active_storage_overview.html#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)
 
```ruby:db/schema.rb
#中略
ActiveRecord::Schema.define(version: 20xx_xx_xx_xxxxxx) do

  create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

  create_table "active_storage_blobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.bigint "byte_size", null: false
    t.string "checksum", null: false
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end
#以下略
```

##画像を投稿しよう
####アソシエーションの設定
レコードとファイルを1対1の関係で紐づけるために**[has_one_attached](https://railsguides.jp/active_storage_overview.html#has-one-attached)メソッド**を利用します。
今回はレシピ投稿アプリなのでrecipesテーブルに画像ファイルを紐付けます。

```ruby:app/models/recipe.rb
class Recipe < ApplicationRecord
  has_one_attached :image
end
```

####コントローラーの編集
画像の保存を許可するストロングパラメーターにしましょう。

```ruby:app/controllers/recipes_controller.rb
class RecipesController < ApplicationController

#中略

  private
  def recipe_params
    params.require(:recipe).permit(:title, :text, :category_id, :time_required_id, :image) #「:image」を追加
  end
end
````

####投稿画面の編集
画像を投稿するためには**[file_field](https://railsdoc.com/page/file_field)**というメソッドを使用します。[Railsドキュメント](https://railsdoc.com/page/file_field)

```erb:app/views/recipes/new.html.erb
<%= form_with model: @recipe, local: true do |f| %>

#中略
    <div class="form-group">
      <label class="text-secondary">画像</label><br>
      <%= f.file_field :image %>
    </div>

#以下略

<% end %>
```
それでは、投稿してみましょう！
![new.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/4547096a-3e3a-37bc-9343-9f6c624f84b8.png)

保存されているか確認してみましょう。

```ruby:ターミナル
rails c

[1] pry(main)> Recipe.find(1).image
   (0.4ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Recipe Load (0.2ms)  SELECT `recipes`.* FROM `recipes` WHERE `recipes`.`id` = 1 LIMIT 1
=> #<ActiveStorage::Attached::One:0x00007fc173c392e0
 @name="image",
 @record=
  #<Recipe:0x00007fc177cc2780
   id: 53,
   title: "てりやきチキン",
   text:
    "1.　鶏もも肉の両面にフォークで穴を開け、味をしみやすくします。\r\n\r\n2.　ジッパー付き保存袋に鶏もも肉を入れ、調味料を加えてよく揉み込みます。\r\n\r\n3.　鶏もも肉が重ならないよう平らにならして空気を抜き、半分に折って冷凍庫に入れます。※保存期間は2週間です。\r\n\r\n4.　フライパンにサラダ油を引いて中火に熱し、冷蔵庫で半解凍した鶏肉を、皮目を下にして入れます。フタをして5分蒸し焼きにします。裏返し、フタをしてさらに3分焼いたら完成です！",
   category_id: 5,
   time_required_id: 2,
   created_at: Wed, 03 Mar 2021 13:54:36 UTC +00:00,
   updated_at: Wed, 03 Mar 2021 13:54:36 UTC +00:00>>
```

##投稿した画像を表示しよう
####コントローラーの編集

```ruby:app/controllers/recipes_controller.rb
class RecipesController < ApplicationController

#中略

  def show
    @recipe = Recipe.find(params[:id])
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to recipe_path(@recipe) #リダイレクト先をshowに変更
    else
      render :new
    end
  end

#以下略

end
```

####ビューファイルの作成
保存した画像を表示するには**[image_tag](https://railsdoc.com/page/image_tag)メソッド**を使います。[Railsドキュメント](https://railsdoc.com/page/image_tag)
基本的な使い方は以下のとおりです。

```erb:例
<%= image_tag 画像ファイルのパス %>

#app/assets/imagesにcooking.jpgを配置している場合
<%= image_tag 'cooking.jpg' %>

#モデルから画像ファイルを呼び出す場合
<%= image_tag @recipe.image %>
```
それではビューファイルを作成していきましょう。

```:ターミナル
touch app/views/recipes/show.html.erb
```

show.html.erbを以下のように編集します。

```erb:app/views/recipes/show.html.erb
<div class="text-center">
  <div class="card recipe-card ">
    <%= image_tag @recipe.image, class: "card-img-top" %>
    <div class="card-body">
      <div class="recipe-name">
        <%= @recipe.title %>
      </div>
      <div class="recipe-content">
        カテゴリー: <span class="recipe-category"><%= @recipe.category.name %></span>
        所要時間: <span class="recipe-time"><%= @recipe.time_required.name %></span>
      </div>
      <hr>
      <p class="card-text">
        <div class="recipe-title">作り方</div>
        <div class="recipe-text d-flex justify-content-start">
          <%= safe_join(@recipe.text.split("\n"),tag(:br)) %>
        </div>
      </p>
    </div>
  </div>
</div>
```

しかし、このままでは画像が存在しない場合でも、画像を表示する記述が読み込まれて、エラーを引き起こしてしまいます。
この問題を解決するためには**[attached?](https://railsguides.jp/active_storage_overview.html#has-one-attached)
メソッド**を使います。

attached?メソッドとは
ファイルが添付されていればtrue、添付されていなければfalseを返してくれるメソッドです。[Railsガイド](https://railsguides.jp/active_storage_overview.html#has-one-attached)

それでは、ビューファイルの修正をしましょう。

```erb:app/views/recipes/show.html.erb
#中略
<div class="card recipe-card ">
  <%= image_tag @recipe.image, class: "card-img-top" if @recipe.image.attached?%>
  <div class="card-body">
#以下略
```

以上で完成になります。
実際に投稿が表示されるか確認してみましょう。
![35752e8fc8e7033eb1122f524ce04b35.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/839eb2ab-54f7-d0d1-dd67-b0507b29bd2f.gif)