Ruby on Rails 5.2の新機能（Active Storage, Content Security Policyなど）

## はじめに

今年（2017年）の4月末にRuby on Rails (Rails)の[バージョン5.1がリリース](http://weblog.rubyonrails.org/2017/4/27/Rails-5-1-final/)されましたが、わずか7ヵ月後の11月末に早くも[バージョン5.2のベータ版がリリース](http://weblog.rubyonrails.org/2017/11/27/Rails-5-2-Active-Storage-Redis-Cache-Store-HTTP2-Early-Hints-Credentials/)されています。

Rails 5.1が、Webpack / Yarnのサポートなどフロントエンドの新機能を中心としたリリースだったのに対し、Rails 5.2は、ファイルアップロード用のActive Storageなどバックエンドの新機能を中心としたリリースになるようです。

本記事では、[GitHubのRailsプロジェクト](https://github.com/rails/rails)の[Issues](https://github.com/rails/rails/issues)や[Pull Requests](https://github.com/rails/rails/pulls)の履歴、Railsの[公式ブログ](http://weblog.rubyonrails.org/)の記事をもとに、Rails 5.2の主要な新機能について、まとめています。

※ 他のバージョンのRailsの主要な新機能・機能追加・変更点については以下を参照してください。

- [今から知っておきたいRails 5の新機能・変更点 - Qiita](https://qiita.com/ryohashimoto/items/09724ad4dddcda01bce3)
- [ReactやwebpackもサポートしたRails 5.1の新機能・変更点 - Qiita](https://qiita.com/ryohashimoto/items/a8ba7bb8d6f340894188)
- [Ruby on Rails 6の主要な新機能・機能追加・変更点](https://qiita.com/ryohashimoto/items/622c3bcfb3336cb9317e)

## Active Storage

[Active Storage](https://github.com/rails/rails/blob/d3893ec38ec61282c2598b01a298124356d6b35a/activestorage/README.md)はファイルアップロードの新機能で、以前から利用されてきた[Paperclip](https://github.com/thoughtbot/paperclip)や[Carrierwave](https://github.com/carrierwaveuploader/carrierwave)などのサードパーティのGemの代わりとなるものです。

アップロード・参照先としては、[Amazon S3](https://aws.amazon.com/jp/s3/), [Google Cloud Storage](https://cloud.google.com/storage/), [Microsoft Azure Blob Storage](https://azure.microsoft.com/ja-jp/services/storage/blobs/)などのクラウドサービスを標準で利用することができ、また、ダイレクトアップロードという、ブラウザから直接クラウドサービスへアップロードできる機能や、ミラーリング機能を備えます。

アップロードするファイルが画像の場合はオンデマンドでサイズの変換([MiniMagick](https://github.com/minimagick/minimagick)を使用)が可能であり、動画やPDFの場合はプレビューを生成することが可能です。どのファイルの場合も、メタデータの取得はジョブによって非同期で行われます。

Railsアプリケーションから使用する場合は、以下のコマンドを実行し、ファイル情報と関連づけの格納に必要なテーブルをDBに作成します。

```
$ bin/rails active_storage:install
$ bin/rails db:migrate
```

ファイル情報と関連づけに使用するモデルは以下のようになっています。
（各モデルは、ActiveStorageの内部で定義されており、実装し直す必要はありません。）

* [Blob](https://github.com/rails/rails/blob/master/activestorage/app/models/active_storage/blob.rb)
  - ファイル情報（Content-Type、サイズ、クラウドでの識別用のkeyやメタデータなど）を格納
* [Attachment](https://github.com/rails/rails/blob/master/activestorage/app/models/active_storage/attachment.rb)
  - ファイルと他のモデルとの関連づけ用の中間テーブル

Railsアプリケーション側のモデルからは、以下のように`has_many_attached`メソッドを使用します。以下は`Article`モデルに`images`という名前で複数のファイルをアップロードする場合の例です。

```ruby
class Article < ApplicationRecord
  has_many_attached :images
```

単一のファイルをアップロードする場合は、`has_one_attached`メソッドを使用します。

```ruby
class Article < ApplicationRecord
  has_one_attached :image
```

その他の詳細な内容や各クラウドへアップロードする場合の設定に関しては、公式のドキュメントや、[こちらのスライド](https://speakerdeck.com/ryohashimoto/whats-new-in-ruby-on-rails-5-dot-2)を参照してください。

* [rails/activestorage at master](https://github.com/rails/rails/tree/master/activestorage)

## Redisキャッシュストア

Railsではこれまでメモリ、ファイル、[Memcached](https://memcached.org/)を利用したキャッシュ機構を提供していましたが、これに加えて、[Redisを利用したキャッシュ機構](https://github.com/rails/rails/pull/31134)を提供するようになりました。

Redisへのキャッシュは使われてから最も長い時間が経ったもの、参照される頻度が最も低いものを優先的に無効にします。

（Rails 5.2ではキャッシュのキーの[再利用](https://github.com/rails/rails/pull/29092)・[圧縮](https://github.com/rails/rails/pull/31147)も行われるようになったので、Redisによるキャッシュと組み合わせると、以前より高い効率でのキャッシュが可能になりました。）

標準ではキャッシュの読み書きに失敗した場合は、例外でなく`nil`（キャッシュにヒットしなかった）とみなすので、設定を正しく行えばこれまでのアプリケーションと変わりなく使用することができます。

Redisのバックエンドとしては、[redis](https://github.com/redis/redis-rb) Gemだけでなく、[hiredis](https://github.com/redis/hiredis)や[Redis::Distributed](https://github.com/redis/redis-rb/blob/master/lib/redis/distributed.rb)をサポートします。

Railsアプリケーションで利用するには、各環境の設定ファイルで以下のように設定を行います。

```ruby
# config/environments/production.rb
config.cache_store = :redis_cache_store
```

より詳しい内容（各バックエンドを利用する方法など）については、公式ドキュメントを参照してください。

* [Built-in Redis cache store #31134](https://github.com/rails/rails/pull/31134)
* [Caching with Rails: 2.6 ActiveSupport::Cache::RedisCacheStore](http://edgeguides.rubyonrails.org/caching_with_rails.html#activesupport-cache-rediscachestore)

## HTTP/2 Early Hints

Early Hintsは、Webサーバーにリクエストが完了する前にヘッダで必要なJavaScriptやスタイルシート（アセット）のURLを伝えることができ、より高速なページの表示を可能にします。

RFC 8297の[An HTTP Status Code for Indicating Hints](https://tools.ietf.org/html/rfc8297) によると、最初にステータス103でLinkヘッダにアセットのURLを埋め込んだレスポンスを返し、次に実際のボディと同じLinkヘッダを含むレスポンスを返します。アセットの各ファイルはサーバーから[HTTP/2 Server Push](https://developers.google.com/web/fundamentals/performance/http2/?hl=ja#_7)によってクライアントに送信されます。

```http
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
```
```http
HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
```

Railsでは、HTTPのLinkヘッダに、`javascript_include_tag` / `stylesheet_link_tag` に記述されたファイルのURLを埋め込みます。

使用するには、[h2o](https://github.com/h2o/h2o)と[pumaの新しいバージョン(3.11.0以上)](https://github.com/puma/puma)のセットアップを行った後に、`bin/rails s`に`--early-hints`オプションをつけて起動します。

* [Implement H2 Early Hints for Rails](https://github.com/rails/rails/pull/30744)
* [eileen.codes | HTTP2 Early Hints](http://eileencodes.com/posts/http2-early-hints/)

## Bootsnap

Bootsnapと呼ばれるRailsアプリケーションの起動時間を短縮するライブラリが、`Gemfile` に含まれるようになりました。

[Bootsnapのサイト](https://github.com/Shopify/bootsnap)によると起動時間が半分程度になるそうです。

* [Add bootsnap to default Gemfile #29313](https://github.com/rails/rails/pull/29313)
* [Shopify/bootsnap at master](https://github.com/Shopify/bootsnap)

## Content Security Policy

[Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)は、サーバーがHTTPヘッダに`Content-Security-Policy`の内容を含むレスポンスを返すことで、ブラウザ側でクロスサイトスクリプティング（XSS）やパケット盗聴攻撃を防ぐ仕組みのことです。

Rails 5.2ではサードパーティのGem（[secureheaders](https://github.com/twitter/secureheaders)など）を使用しなくても、CSPの設定が簡単にでき、セキュリティを強化できるようになりました。

RailsアプリケーションでCSPを設定するには、`config/initializers/content_security_policy.rb` で以下のように設定します。

```ruby
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self, :https # サイト自身のドメインとHTTPSを使用した全ドメイン
  policy.style_src   :self, :https, :unsafe_inline # インラインの<style>要素を許可（危険）
  ...
end
```

コントローラで設定をカスタマイズするには、`content_security_policy`メソッドを使用します。

```ruby
class ArticlesController < ApplicationController
  content_security_policy do |policy|
    policy.upgrade_insecure_requests true # httpをhttpsへリダイレクト(対応ブラウザの場合）
  end
end
```

* [Add DSL for configuring Content-Security-Policy header #31162](https://github.com/rails/rails/pull/31162)
* [Content Security Policy (CSP) - Web セキュリティ | MDN](https://developer.mozilla.org/ja/docs/Web/Security/CSP)

## 暗号化されたCredentials

これまで、複数存在していたRailsアプリケーション内の機密情報(`config/secrets.yml`や、`SECRET_BASE_KEY`)を1つのファイル(`config/credentials.yml.enc`)内に暗号化して格納できるようになりました。

（これによって、Rails 5.1で導入された、[encrypted secrets](https://github.com/rails/rails/pull/28038)も置き換えられてしまいました。）

ファイルの復号には、環境変数`RAILS_MASTER_KEY`を使用し、`bin/rails credentials:edit`コマンドで内容を編集を行うことができます。

`config/credentials.yml.enc`の中身は例えば、以下のようになっています。

```yaml
aws:
  access_key_id: XXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXX
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

ファイルを保存すると、自動的に再度暗号化されます。

* [Add credentials using a generic EncryptedConfiguration class #30067](https://github.com/rails/rails/pull/30067)


## まとめ

Rails 5.2はファイルアップロードを簡単に行えるActive StorageやRedisキャッシュストアなど、バックエンド周りの新機能を追加したバージョンとなるようです。
また、Content Security Policyの設定や暗号化されたCredentialsの管理も可能になっており、よりセキュアなアプリケーションの構築が可能になっています。
来年のリリースに向けて、新機能を試してみたり、既存のアプリケーションのアップグレードをしてみてはいかがでしょうか。

## 参考サイト

* [Rails 5.2.0 beta: Active Storage, Redis Cache Store, HTTP/2 Early Hints, CSP, Credentials](http://weblog.rubyonrails.org/2017/11/27/Rails-5-2-Active-Storage-Redis-Cache-Store-HTTP2-Early-Hints-Credentials/)
* [Riding Rails](http://weblog.rubyonrails.org/)
* [rails/rails](https://github.com/rails/rails)