ActiveStorageを使って画像アップロードする

## 環境

- OS: macOS 12.3.1（21E258)
- Ruby: 3.1.3
- Rails: 7.0.4


## Railsアプリの作成する

```
rails new images
```

## Active Storageのインストール

```
rails active_storage:install
Copied migration 20221227045939_create_active_storage_tables.active_storage.rb from active_storage
```

## マイグレーション

```
rails db:create db:migrate
Database 'db/development.sqlite3' already exists
Database 'db/test.sqlite3' already exists
== 20221227045939 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {:id=>:primary_key})
   -> 0.0010s
-- create_table(:active_storage_attachments, {:id=>:primary_key})
   -> 0.0008s
-- create_table(:active_storage_variant_records, {:id=>:primary_key})
   -> 0.0005s
== 20221227045939 CreateActiveStorageTables: migrated (0.0023s) ===============
```

## 最初の画面を作る

今回は写真を投稿する画面を1つだけ作る。

- routes.rbにrootを追加

```ruby
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  root "posts#new"
end
```

- app/controllers/posts_controller.rbを作成

```ruby
class PostsController < ApplicationController
  def new
  end
end
```

- app/views/posts/new.html.erbを作成

```ruby
<h1>Example Image Uploader</h1>
```

## 動作確認

```
bin/rails s
```

## 画像を保持するモデルを作成

ここでは、Post(投稿)というモデルを作って、1つのPostに対して複数の写真(photos)をアップロードできるようにする。

```
rails g model Post                                             
      invoke  active_record
      create    db/migrate/20221227051214_create_posts.rb
      create    app/models/post.rb
      invoke    test_unit
      create      test/models/post_test.rb
      create      test/fixtures/posts.yml
```

- app/models/post.rbを開き、has_many_attachedを追加

```ruby
class Post < ApplicationRecord
  has_many_attached :photos
end
```

## マイグレーションを実行する

```
rails db:migrate

== 20221227051214 CreatePosts: migrating ======================================
-- create_table(:posts)
  TRANSACTION (0.1ms)  begin transaction
   (1.7ms)  CREATE TABLE "posts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL)
   -> 0.0041s
== 20221227051214 CreatePosts: migrated (0.0042s) =============================
```

## 画面にアップロードするフォームを作成

- app/views/posts/new.html.erb

```ruby
<h1>Example Image Uploader</h1>

<%= form_with model: @post do |f| %>
  <%= f.file_field :photos %>
  <%= f.submit %>
<% end %>

```

動作確認する。こんな感じで表示される。

<kbd>![Images (1)](https://user-images.githubusercontent.com/220267/209615703-fb911e6c-803d-4c70-baa5-ce4e247c15f0.png)</kbd>

## アップロードの処理を書く

- config/routes.rbに`POST /posts`のルーティングを追加

```ruby
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  root "posts#new"

  resources :posts, only: [:new, :create]
end
```

- app/controllers/post_controller.rbを作成。`#new`メソッドと`#create`メソッドを書く

```ruby
class PostsController < ApplicationController

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    @post.save
    redirect_to root_path
  end

  private
  
  def post_params
    params.require(:post).permit(:photos)
  end
end

```

## Postにフィールドを追加する

バリデーションエラーが発生した場合の動作のチェックのためにPostモデルにフィールドを追加する。
ここではタイトル(title)という文字列フィールドを追加することにする。タイトルは20文字を超えるとバリデーションでエラーになるようにする。

```
rails g migration AddTitleColumnToPosts
```

- config/db/..._add_title_column_to_posts.rb

```ruby
class AddTitleColumnToPosts < ActiveRecord::Migration[7.0]
  def change
    add_column :posts, :title, :string
  end
end
```

- マイグレーション

```
rails db:migrate
== 20221227054700 AddTitleColumnToPosts: migrating ============================
-- add_column(:posts, :title, :string)
   -> 0.0010s
== 20221227054700 AddTitleColumnToPosts: migrated (0.0010s) ===================
```

- app/models/post.rb

バリデーションを追加

```ruby
class Post < ApplicationRecord
  has_many_attached :photos

  validates :title, presence: true, length: { maximum: 20 } # ここを追加
end

```

- app/controllers/posts_controller.rb

バリデーションエラーの時はリダイレクトではなく、renderするように変更

```ruby
...
  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to new_post_path
    else
      render :new, status: 400
    end
  end
  ...
```

- app/views/posts/new.html.erb 

画面にバリデーションエラーの表示を追加

```ruby
<h1>Example Image Uploader</h1>

<%= form_with model: @post do |f| %>
  <ul>
    <% @post.errors.full_messages.each do |m| %>
      <li><%= m %></li>
    <% end %>
  </ul>
  <div><%= f.text_field :title %></div>
  <div><%= f.file_field :photos %></div>
  <div><%= f.submit %></div>
<% end %>
```