Rails6.0のActive Storageに関する変更点まとめ

Rails5.2から登場したActive Storageですが、Rails6.0になっていくつかの変更があったのでまとめました。

# 1. 画像形式の`BMP`と`tiff`と`pjepg`をサポート
Active Storageがサポートする画像形式に、以下の3つが追加されました。

## 1-1. `BMP`
Windows端末で標準的な画像の保存形式です。画像を点の集まりとして記録しています。
拡張子は`.bmp`です。

- 参照
  - [Add support for generating BMP image variants. ](https://github.com/rails/rails/pull/36051)

## 1-2. `tiff`
`Tagged Image File Format`の略で、ビットマップ画像の符号化形式の一つです。 タグと呼ばれる識別子を使うことによって、様々な形式のビットマップ画像を柔軟に表現できます。
拡張子は`.tif`または`.tiff`です。

- 参照
  - [Add support for generating TIFF image variants.](https://github.com/rails/rails/pull/34824)

## 1-3. `progressive JPEG`
まずイメージ全体を荒い低解像度で表示し、徐々に細かい高解像度で表示していく方式のJPEGです。
拡張子は`.jpg`または`.jpeg`です。

- 参照
  - [Add support for generating progressive JPEG image variants.](https://github.com/rails/rails/pull/34455)

# 2. Active Storageが生成するroutesのprefixが指定可能に
Rails5.2までは、Active Storageが生成するroutesのprefixは`/rails/active_storage`で固定でしたが、Rails6.0では任意のprefixが設定できるようになりました。

- 参照
  - [Add ActiveStorage.routes_prefix for configuring the Active Storage generated routes.](https://github.com/rails/rails/pull/33883)

## 2-1. デフォルトの場合
```bash
$ bundle exec rails routes -c active_storage
                   Prefix Verb URI Pattern                                                                              Controller#Action
       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
```

## 2-2. prefixに`/files`を指定した場合
```ruby:/config/application.rb
module RailsApp
  class Application < Rails::Application
...
    config.active_storage.routes_prefix = '/files'
  end
end
```
```bash
$ bundle exec rails routes -c active_storage
                   Prefix Verb URI Pattern                                                               Controller#Action
       rails_service_blob GET  /files/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET  /files/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET  /files/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT  /files/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST /files/direct_uploads(.:format)                                           active_storage/direct_uploads#create
```

# 3. アップロードしたファイルがストレージに保存されるタイミングがsave時に変更
Rails5.2までは、インスタンスのattributeに代入した時点で、アップロードしたファイルがストレージに保存されていました。
Rails6.0では、saveしたタイミングでストレージに保存されるよう変更になっています。saveトランザクションがコミットされた後に、ストレージに保存されます。

- 参照
  - [Store newly-uploaded files on save rather than assignment](https://github.com/rails/rails/pull/33303)

## 3-1. Rails5.2まではインスタンスのattributeに代入したタイミング
```ruby
@user.avatar = params[:avatar]
```

## 3-2. Rails6.0ではsaveしたタイミング
```ruby
@user.save!
```

# 4. `ActiveStorage::Reflection`モジュールが追加
新たに追加された`ActiveStorage::Reflection`モジュールの`reflect_on_all_attachments`や`reflect_on_attachment`といったメソッドを使うことで、定義されたattachmentsの情報を取得することができます。

- 参照
  - [Add the ability to reflect on defined attachments using the existing Active Record reflection mechanism. ](https://github.com/rails/rails/pull/33018)
 
```ruby:app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :highlights
end
```
このようなUserモデルがあった場合、`ActiveStorage::Reflection#reflect_on_attachment`メソッドと`ActiveStorage::Reflection#reflect_on_all_attachments`メソッドを使うと、それぞれ以下のような挙動になります。
## 4-1. `ActiveStorage::Reflection#reflect_on_attachment`
```ruby
reflection = User.reflect_on_attachment(:avatar)
# => #<ActiveStorage::Reflection::HasOneAttachedReflection:0x00007f9e26c37460 @name=:avatar, @scope=nil, @options={:dependent=>:purge_later}, @active_record=User (call 'User.connection' to establish a connection), @klass=nil, @plural_name="avatars">
reflection.name
# => :avatar
reflection.macro
# => :has_one_attached
```

## 4-2. `ActiveStorage::Reflection#reflect_on_all_attachments`
```ruby
reflections = User.reflect_on_all_attachments
=> [#<ActiveStorage::Reflection::HasOneAttachedReflection:0x00007fbedcaf9608 @name=:avatar, @scope=nil, @options={:dependent=>:purge_later}, @active_record=User (call 'User.connection' to establish a connection), @klass=nil, @plural_name="avatars">, #<ActiveStorage::Reflection::HasManyAttachedReflection:0x00007fbedcab50c0 @name=:highlights, @scope=nil, @options={:dependent=>:purge_later}, @active_record=User (call 'User.connection' to establish a connection), @klass=nil, @plural_name="highlights">]
reflections[0]
=> #<ActiveStorage::Reflection::HasOneAttachedReflection:0x00007f9e26c37460 @name=:avatar, @scope=nil, @options={:dependent=>:purge_later}, @active_record=User (call 'User.connection' to establish a connection), @klass=nil, @plural_name="avatars">
reflections[0].name
=> :avatar
reflections[0].macro
=> :has_one_attached
reflections[1]
=> #<ActiveStorage::Reflection::HasManyAttachedReflection:0x00007fbedcab50c0 @name=:highlights, @scope=nil, @options={:dependent=>:purge_later}, @active_record=User (call 'User.connection' to establish a connection), @klass=nil, @plural_name="highlights">
reflections[1].name
=> :highlights
reflections[1].macro
=> :has_many_attached
```

# 5. `ActiveStorage::Blob#open`メソッドが追加、`ActiveStorage::Downloading`モジュールが非推奨
`ActiveStorage::Blob#open`というメソッドが追加されました。
blobをアップロード後に、何かしら処理を加えたい場合に使います。
blobをtmpファイルとして保持しており、ウイルススキャンやメディアトランスコーダによる変換など、外部プログラムを使った処理を実行することもできます。

```ruby
message.video.open do |file|
  system '/path/to/virus/scanner', file.path
  # ...
end
```
また、元々このような形で使う想定で用意されていた`ActiveStorage::Downloading`モジュールが非推奨になっています。

- 参照
  - [Add ActiveStorage::Blob#open](https://github.com/rails/rails/commit/ee21b7c2eb64def8f00887a9fafbd77b85f464f1)
  - [Downloading Files](https://github.com/rails/rails/blob/v6.0.0.rc1/guides/source/active_storage_overview.md#downloading-files)

# 6. Google Cloud Storageのストリーミングダウンロードをサポート
`google-cloud-storage` gemのバージョンが、1.8以上から1.11以上に引き上げられました。
これにより、ストリーミングダウンロードに対応するようになりました。

- 参照
  - [Support streaming downloads from Google Cloud Storage](https://github.com/rails/rails/pull/32788)

# 7. `image_processing` gemの導入
ファイルを変換するためのgemが、`mini_magick `から`image_processing`に変更されました。
`image_processing`は`mini_magick`と`ruby-vips`のwrapperで、画像のリサイズや加工を行います。

- 参照
  - [Use ImageProcessing gem for ActiveStorage variants](https://github.com/rails/rails/pull/32471)
  - [Transforming Images](https://github.com/rails/rails/blob/v6.0.0.rc1/guides/source/active_storage_overview.md#transforming-images)

Rails 6.0.0.rc1では、Gemfileにおいてデフォルトでコメントアウトされています。

```ruby:Gemfile
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
```
コメントアウトを外して、`bundle install`しましょう。

# 8. 既存の画像を置き換え
アタッチされたモデルを`update`または`update！`で更新すると、新たに画像を追加するのではなく既存の画像を置き換えます。

```ruby
@user.update!(images: [ … ])
```

- 参照
  - [Store newly-uploaded files on save rather than assignment](https://github.com/rails/rails/pull/33303)