ActiveStorage

# ファイルアップロード機能
ファイルをアップロードするためにActiveStorageを使用する。
これはRails5.2から追加されたファイルアップロード機能
S3やGoogleCloudにもダイレクトアップロードが可能なようです。

ただ、rails 

まずは使用前に以下を実行
このコマンドでActiveStrageを使用するために必要なマイグレーションファイルが作成される。
```
bin/rails active_storage:install
```

以下を実行してDBに変更を反映。
```
bin/rails db:migrate
```

:::note info
bin/rails sとrails sは何が違うの?

bin/rails s と rails s の違いは、Railsアプリケーションを起動する際に使用するコマンドの形式にあります。

bin/rails s は、Railsアプリケーションのルートディレクトリにある bin ディレクトリ内にある rails コマンドを実行する形式です。これは、Railsアプリケーションのディレクトリ内でのみ実行されるコマンドであり、アプリケーションのローカルインストールに関連しています。bin/rails コマンドは、アプリケーションのバージョンや設定を考慮して正しい環境で Rails サーバーを起動します。

一方、rails s は、Railsのグローバルインストールがされている場合に使用されるコマンドです。グローバルインストールされた Rails は、システム全体で利用可能なので、任意のディレクトリで rails s を実行することができます。ただし、グローバルインストールは推奨されないことが多く、Railsアプリケーションのディレクトリ内に bin/rails を使ってローカルインストールすることが一般的です。

したがって、bin/rails s は Rails アプリケーションのディレクトリ内でのみ実行可能であり、アプリケーションのバージョンや設定を考慮して正しい環境を使用します。一方、rails s はグローバルインストールされた Rails を使用してサーバーを起動しますが、グローバルインストールは推奨されない場合があります。
:::

Active Storageは、データベースで「active_storage_blobs」と「active_storage_attachments」という名前の2つのテーブルを使用します。

active_storage_blobs：アップロードファイルのメタ情報を管理するモデル
active_storage_attachments：該当モデルとblobsの中間テーブルに該当するモデル

$ rails g resource user name:string
$ rails db:migrate

# 1つの添付ファイルの場合
Userモデルに1つの画像を添付するには、has_one_attachedを使う。
（あくまでも使い方の確認なので、もちろん普通はUserをnameだけでは作成しません。）

Userクラスに移動して、
```
class User < ApplicationRecord
#avatarの部分は任意の文字列でオッケー。
  has_one_attached :avatar
end
```
こんな感じで記述する。こうすると、User.imageを使えるようになる。

そして、controllerとviewを編集する。

```users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.create params.require(:name).permit(:name, :avatar) 
    redirect_to @user
  end

  def show
    @user = User.find(params[:id])
  end

  def edit
    @user = User.find(params[:id]) 
  end

  def update
    @user = User.find(params[:id])
    @user.update params.require(:user).permit(:name, :avatar) 
    redirect_to @user
  end
end
```
```new.html.erb
<%= form_with model: @user, local: true  do |f| %>
  <%= f.text_area :name %><br>
  <%= f.file_field :avatar %><br>
  <%= f.submit %>
<% end %>
```
```show.html.erb
#attached?メソッドを使えるようになるので、これでuserに画像が保存されているかどうか確認できます。
<% if @user.avatar.attached? %>
  <%= image_tag @user.avatar %>
<% end %>
```

複数ファイルの場合は以下の３点を変更すればOKです。
* has_one_attachedをhas_many_attachedに変更
* user.avatarの代わりにuser.avatarsを使う
* file_fieldにmultiple: trueを追記


# ファイルの保存先の変更
以下の2つのファイルを変更しましょう。
* config/environments/development.rb
* production.rb

```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
```

デフォルトは以下の保存先は:localです。
* 開発環境(development)
* 本番環境(production)

localはconfig/storage.ymlに設定されています。
ここの設定を見ればどのディレクトリに保存されるか確認できます。

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

#ここですね。つまり、ローカルディスクで、アプリ直下の/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) %>
```

でもローカルを保存先にするのは個人開発だと良いですが、そうでない場合はよろしくないですね。
したがって、AWSのS3などクラウドに保存するとよいです。

その場合は、コメントアウトされている部分を設定していけば変更できるようになっています。ただし、他のサービスとの連携にはgemが必要になります。

S3の場合は、まずは以下のgemをインストールしましょう。
```Gemfile
gem "aws-sdk-s3", require: false
```

そして、例えばS3に保存するのであれば、先ほどのファイルのコメントアウトを外して必要項目を設定していきましょう。
```
# 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
```

```
$ EDITOR=code rails credentials:edit
```

上の例ではVScodeが開きます。
* Atom EDITOR=atom
* SublimeText EDITOR=subl
* Vim EDITOR=vim

```
aws:
 access_key_id: 123 #自分のアクセスキー
 secret_access_key: 456 #自分のシークレットアクセスキー
```

これまではsecrets.ymlを使用していた方もいるかと思いますが、Rails5.2以降はRails Credentialsを使用する方がよいようです。
<%= Rails.application.credentials.dig(...) %>の部分は、先ほどVScodeなどでん入力したCredentialsのデータを読み込んでいますのでコメントをそのまま使いましょう。

:::note info
入力した内容はconfig/master.keyを用いて暗号化され、config/credentials.yml.encが生成されます。復号された中身は $ rails credentials:show で確認できます。
:::

ただ、S3の使用などはAWSを学習しつつやらないと何のことか分からないかと思いますので、並行で調べることをおすすめします。


# ActiveStorageのバリデーション　
ActiveStorageにはバリデーション用のヘルパーメソッドがないためgemを使用する。
```
gem 'active_storage_validations', '~> 0.8.8'
```

```app/models/event.rb
class Event < ApplicationRevord
  has_one_attached :image
  has_many :tickets, dependent: :destroy
  belongs_to :owner, class_name: "User"

  #この設定でpngとjpeg以外の画像アップロードをするとバリデーションエラーになる
  #これはファイル名ではなく、中身を見て判断されるので、テキストファイルに.pngと拡張子つけてもエラーとなる
  validates :image, 
    content_type: [:png, :jpg, :jpeg],
#データサイズのバリデーション
    size: { less_than_or_equal_to: 10.megabytes }
#画像の大きさのバリデーション
    dimension: { width: {max: 2000 }, height: { max: 2000 } }
略
```

ビュー側では以下のように書くと、
* blobという属性が存在するかどうか
* それがデータベースに永続化されているかどうか
を確認できる。つまり、実際に画像がアップロードされたかどうかを確認している。
```app/views/events/edit.html.erb
@event.image.blob&.persisted?
```

ただし、ダイレクトアップロードの場合、バリデーションが不完全なので使用には検討の余地あり。