Ruby on Rails 6の主要な新機能・機能追加・変更点

## はじめに

ここ数年は毎年リリースされてきた[Ruby on Rails](https://rubyonrails.org/) (以下Rails)の新しいバージョンですが、今回はメジャーバージョンアップグレードの6.0（以下Rails 6)となり、[2019年8月にリリース](https://weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/)されました。

Rails 6では、[Action Text](https://github.com/rails/actiontext)と[Action Mailbox](https://github.com/rails/actionmailbox)の新しい2つのフレームワークの導入を始め、複数データベースや並列テストへの対応など、メジャーバージョンアップグレードにふさわしい大きな機能追加が行われています。

本記事では、GitHubのRailsプロジェクトの[Issues](https://github.com/rails/rails/issues)や[Pull Requests](https://github.com/rails/rails/pulls)の内容をもとに、Rails 6の主要な新機能・機能追加・変更点の紹介を行います。

※ 以前のバージョンの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 5.2の新機能（Active Storage, Content Security Policyなど） - Qiita](https://qiita.com/ryohashimoto/items/75f48fa5a3846c58f735)

## 注意点

### Rubyのバージョン

Rails 6を動かすには、Ruby 2.5以上が必要になります。Ruby 2.6も対応が行われています。

### セキュリティアップデート

Rails 6のリリース後は、Railsのバージョン6.0系列と、5.2系列がセキュリティアップデートの対象となります。それより前のバージョンについても、セキュリティアップデートが提供される可能性がありますが、保証はありません。

**参考**

- [Timeline for the release of Rails 6.0 | Riding Rails](https://weblog.rubyonrails.org/2018/12/20/timeline-for-the-release-of-Rails-6-0/)
- [Require Ruby 2.5 for Rails 6. by kaspth · Pull Request #34754 · rails/rails](https://github.com/rails/rails/pull/34754)

## 新機能

### Action Text

[Action Text](https://github.com/rails/actiontext)は、Railsアプリケーション内で画像やファイルを含むリッチテキストの編集を可能にするフレームワークです。Action Text自体は[Trix editor](https://trix-editor.org/)というJavaScriptのライブラリと、[Active Storage](https://github.com/rails/rails/tree/master/activestorage)を利用しています。

Action Textを使用するには、モデルで`has_rich_text`をリッチテキストを使用する属性（今回の例では`content`）に対して指定を行います。

```ruby:app/models/message.rb
class Message < ApplicationRecord
  has_rich_text :content
  ...
```

フォームでリッチテキストの編集フィールドを使用するには、以下のように`rich_text_area`メソッドを使用します。

```erb:app/views/messages/_form.html.erb
<%= form_with(model: message) do |form| %>
  ...
  <div class="field">
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
  ...
<% end %>
```

表示を行うには、以下のように元の属性（`content`）を使用します。

```erb
<%= @message.content %>
```

コントローラ側でも、元の属性（`content`）をStrong Parameterで指定するだけで使用できるようになっています。

```ruby:app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def create
    message = Message.new(message_params)
    if message.save 
    ...
  end

  private

    def message_params
      params.require(:message).permit(:title, :content)
    end
end
```

**参考**

- [rails/actiontext: Edit and display rich text in Rails applications](https://github.com/rails/actiontext)
- [Introducing Action Text for Rails 6 | Riding Rails](https://weblog.rubyonrails.org/2018/10/3/introducing-action-text-for-rails-6/)

### Action Mailbox

[Action Mailbox](https://github.com/rails/actionmailbox)はRailsアプリケーション内でメールの受信や表示、転送や破棄などのライフサイクル追跡などの処理を行うフレームワークです。

メールの受信は、[SendGrid](https://sendgrid.kke.co.jp/)などの外部サービスからだけでなく、[Postfix](http://www.postfix.org/)からも行うことができます。メールの受信自体は、RailsアプリケーションへのHTTP POSTによって行われます。（各サービスでのURLへの設定が必要になります。）

受信されたメールの内容（from:など）を元にルーティングが生成されます。
各メールは`ActionMailbox::InboundEmail`というモデルとして取り扱われ、アプリケーションでの表示や処理が可能になります。Active Storageを利用して、[Amazon S3](https://aws.amazon.com/jp/s3/)などのクラウドストレージに保存することも可能です。

各メールは受信後に指定日数（デフォルトで30日）で、`ActionMailbox::IncinerationJob`によって焼却（破棄）が行われます。

**参考**

- [rails/actionmailbox: Receive and process incoming emails in Rails](https://github.com/rails/actionmailbox)
- [Introducing Action Mailbox for Rails 6 | Riding Rails](https://weblog.rubyonrails.org/2018/12/13/introducing-action-mailbox-for-rails-6/)

### 複数データベース

Active Recordの機能拡張とアプリケーションの設定項目、Rakeタスクの追加により、複数のデータベースを利用することができるようになりました。

例えば、以下のように設定することで書き込み/読み込み/特定のデータの読み込みを行うデータベースを分けることが可能になります。
（`development`の直下にspecification (spec)と呼ばれる`primary`、`readonly`、`animals`を追加しています。）

```ruby:config/database.yml
development:
  primary:
    <<: *default
    database: myapp_development
  readonly:
    <<: *default
    database: myapp_development_readonly
    replica: true
  animals:
    <<: *default
    database: myapp_development_animals
    migrations_paths: "db/animals_migrate"
...
```

モデルからは、以下のように指定して読み込み、書き込みで利用するデータベースを別々に指定することができます。（`writing`、`reading`はroleと呼ばれます。）

```ruby:app/models/venue.rb
class Venue < ApplicationRecord
  connects_to database: { writing: :primary, reading: :readonly }
  ...
```

```ruby:app/models/animals_model.rb
class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :animals, reading: :animals }
  ...
```

```ruby:app/models/animal.rb
class Animal < AnimalsModel
  ...
```


以下のようにspecificationを指定したRakeタスクを使用することができます。

```console
rails db:create:animals
rails db:migrate:animals
rails db:drop:animals
```

**参考**

- [RailsConf 2018 | The Future of Rails 6: Scalable by Default - Speaker Deck](https://speakerdeck.com/eileencodes/railsconf-2018-the-future-of-rails-6-scalable-by-default)
- GitHubのPull Request: [Part 1](https://github.com/rails/rails/pull/32274), [Part 2](https://github.com/rails/rails/pull/33637), [Part 3](https://github.com/rails/rails/pull/33770), [Part 4](https://github.com/rails/rails/pull/34052)

### 並列テスト

デフォルトで並列でテストを行うようになりました。並列にするワーカーの数は以下のようにカスタマイズすることができ、それぞれ別の一時的なデータベースを使用します。また、プロセスで実行するか、スレッドで実行するかを設定することができます。（標準では、ワーカー数は2、別のプロセスで実行）

```ruby:test/test_helper.rb
class ActiveSupport::TestCase
  parallelize(workers: 4, with: :threads) 

  parallelize_setup do |worker|
    # 並列テスト前の処理を設定
  end

  parallelize_teardown do |worker|
    # 並列テスト後の処理を設定
  end
end
```

**参考**

- [RailsConf 2018 | The Future of Rails 6: Scalable by Default - Speaker Deck](https://speakerdeck.com/eileencodes/railsconf-2018-the-future-of-rails-6-scalable-by-default)
- [Parallel testing by eileencodes · Pull Request #31900 · rails/rails](https://github.com/rails/rails/pull/31900)
- [Rails 5.1.5, parallel testing and more! | Riding Rails](https://weblog.rubyonrails.org/2018/2/17/this-week-in-rails-rails-5-1-5-parallel-testing-and-more/)


### 新しいオートローダー（Zeitwerk）の導入

[Zeitwerk](https://github.com/fxn/zeitwerk)と呼ばれる効率的でスレッドセーフなオートローダー（ファイルをクラスやモジュールとして読み込む仕組み）が導入されました。

Zeitwerkは実行時間が遅い`$LOAD_PATH`内のファイルの走査を行わず、独自にプロジェクトツリーの走査を行い、ファイルの相対ファイル名を使用したクラスやモジュールの読み込みを行うことで高速化を実現しています。サブディレクトリは、親ディレクトリがモジュールになっているときのみに遅延してロードされます。

Zeitwerkの導入に伴って、アプリケーションの起動やファイルのリロードが改善されますが、従来のオートローダーの仕組みと異なるため、既存のアプリケーションなどでクラスの階層とファイルのパスが一致しない場合などは対応が必要になる可能性があります。

**参考**

- [fxn/zeitwerk: Efficient and thread-safe code loader for Ruby](https://github.com/fxn/zeitwerk#reloading)
- [Rails 6.0.0 beta2 released | Riding Rails](https://weblog.rubyonrails.org/2019/2/25/Rails-6-0-beta2-released/)

## 機能追加

### DBへのバルクインサートの標準サポート

1つのSQL文で複数のレコードの挿入を行うバルクインサート（Bulk insert）が実装され、使用できるようになりました。

バルクインサートを実現するGemとしてactiverecord-importなどがこれまで使用されていましたが、Rails 6からは標準で、モデルに対して`insert_all`、`upsert_all`メソッドを呼び出すと、レコードの一括挿入、一括更新が可能になります。

更新に関しては、`unique_by`を指定して、どのカラムが一致すると上書きを行うかを指定することができます。

```ruby
# booksテーブルのレコードを一括挿入
Book.insert_all!([
  { title: "Rework", author: "David" },
  { title: "Eloquent Ruby", author: "Russ" }
])

# booksテーブルのレコードを一括更新
now = Time.current
Book.upsert_all([
  { title: "Rework", author: "David", isbn: "1" },
  { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
], unique_by: :isbn)
```

**参考**

* [Add insert_many to ActiveRecord models #35077](https://github.com/rails/rails/pull/35077)

### `ActiveRecord::Relation#pick`

DB上のテーブルの絞り込み条件や並べ替えを指定した際に、最初のレコードの特定のカラムの値を取得するためのメソッドである`ActiveRecord::Relation#pick`が追加されました。

```ruby
# 最後に作成されたユーザーの名前を取得
User.order(created_at: :desc).pick(:name)

# pickを使わない場合は以下のように書く必要がある
User.order(created_at: :desc).limit(1).pluck(:name).first
```

**参考**

- [Add Relation#pick as short-hand for single-value plucks by dhh · Pull Request #31941 · rails/rails](https://github.com/rails/rails/pull/31941)

### `ActiveRecord::Relation#create_or_find_by` / `#create_or_find_by!`

レコードがなければ作成を行う`ActiveRecord::Relation#find_or_create_by`メソッドの特定の問題を解消するために、先にレコードの作成を行い、ユニーク制約エラーの場合はレコードの参照を行う`#create_or_find_by`メソッドが追加されました。

`#find_or_create_by`メソッドは`SELECT`の後に`INSERT`を行う際に時間差があるため、その間に`SELECT`で取得したレコードが古くなってしまう問題がありました。`#create_or_find_by`メソッドでは、この問題を解消するために先に`INSERT`を行い、ユニーク制約のエラー（`ActiveRecord::RecordNotUnique`)が発生した場合は、`SELECT`でレコードを取得するという挙動にしています。

`#find_or_create_by`の代わりに`#create_or_find_by`メソッドを使用する際には、あらかじめユニーク制約を設定する必要があること、レコードの作成よりも参照が多い場合は処理自体が遅くなる可能性があること、また、レコードが作成できない場合の例外などの挙動の違いを把握しておく必要があります。

```ruby
# loginカラムにユニーク制約が設定されている（nameカラムには設定されていない）。
User.create(login: "bob", name: "Bob Dylan")
# ActiveRecord::RecordNotFound例外が発生(create後のfindで）
User.create_or_find_by(login: "bob", name: "Bob Marley")
# ActiveRecord::RecordNotUnique例外が発生(find後のcreateで)
User.find_or_create_by(login: "bob", name: "Bob Marley")
```

`#create_or_find_by!`メソッドは、レコード作成時に`#create`メソッドの代わりに`#create!`メソッドを呼び出し、バリデーションエラーの際に`ActiveRecord::RecordInvalid`例外を発生させます。

**参考**

- [Add #create_or_find_by to lean on unique constraints by dhh · Pull Request #31989 · rails/rails](https://github.com/rails/rails/pull/31989)

### `String#truncate_bytes`

マルチバイト文字を含む文字列を指定したバイト数で正しく切り捨てを行うためのメソッド`String#truncate_bytes`が追加されました。

```ruby
# 20バイトで切り捨てを行う
"🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
=> "🔪🔪🔪🔪…"
```

**参考**

- [String#truncate_bytes by jeremy · Pull Request #27319 · rails/rails](https://github.com/rails/rails/pull/27319)

### Action Cableのテストが可能に

Action Cableのテストを行うための機能（`ActionCable::TestCase`クラス、`assert_broadcast`メソッドなど）が追加されました。

**参考**

- [Action cable testing by palkan · Pull Request #33659 · rails/rails](https://github.com/rails/rails/pull/33659)

### マイグレーションファイルを生成する対象となるデータベースを指定できるように

複数データベースに関連する変更点ですが、`rails generate migration`を実行するときに`--database`(短縮形は`--db`)オプションを指定することで、マイグレーションファイルの生成対象となるデータベース(のspecification)を指定できるようになりました。オプションを指定しない場合は、`config/database.yml`で設定されたprimaryのデータベースが使用されます。

```console
rails g migration CreateHouses address:string --database=kingston
      invoke  active_record
      create    db/kingston_migrate/20180830151055_create_houses.rb
```

**参考**

- [Add migrations_paths option to migration generator by eileencodes · Pull Request #33760 · rails/rails](https://github.com/rails/rails/pull/33760)
- [Refactor migrations_path command option to database by gmcgibbon · Pull Request #34021 · rails/rails](https://github.com/rails/rails/pull/34021)

### RDBMSを変更するタスクを追加

RDBMSを変更する`db:system:change`タスクが追加されました。

例えば、すでに既存のアプリケーションがSQLiteを使用している場合、以下のようにコマンドを実行することで、PostgreSQLを使用するように変更することができます。

```console
bin/rails db:system:change --to=postgresql
```

**参考**

- [Add rails db:system:change command by gmcgibbon · Pull Request #34832 · rails/rails](https://github.com/rails/rails/pull/34832)

### Active Jobのフックを追加

Active Jobで使用する`enqueue_retry.active_job`、`retry_stopped.active_job`、`discard.active_job`のフックが追加されました。これによって、ジョブがリトライまたは破棄された際のログなどで、対象となるジョブの情報を把握することが容易になりました。

**参考**

- [Add hooks to ActiveJob around retries and discards by steves · Pull Request #33751 · rails/rails](https://github.com/rails/rails/pull/33751)

### `ActiveRecord::Relation#where`の条件で終端なしの`Range`を使用できるように

[Ruby 2.6で終端なしのRangeが使用できるようになった](https://bugs.ruby-lang.org/issues/12912)ので、これに合わせて`ActiveRecord::Relation#where`でも引数の対応が行われ、終端なしのRangeを使用できるようになりました。

```ruby
features.where(awesomeness: 10..) # 10..は10..Float::INFINITYと同じ
```

**参考**

- [Support endless ranges in where by gregnavis · Pull Request #34906 · rails/rails](https://github.com/rails/rails/pull/34906/)

## 変更点

### Webpackerが標準でJavaScriptを処理するように

Webpackerが標準でインストールされるようになり、以下のような変更が行われています。

* 新規アプリケーションではwebpackerのgemがデフォルトでインストールされ、`rails webpacker:install`が実行される。
* Action Cableのジェネレータで生成されるファイルがCoffeeScriptからES6に変更される。
* Active Storage、Action Cable、Turbolinks、Rails-UJSで使用するJavaScriptが`package.json`に依存するnpmモジュールとして含まれるようになり、`app/javascript/application.js`のpackにロードされる。
* Sprockets用のJavaScript関連の機能（圧縮や難読化など）が標準では使用されなくなる。
* Scaffoldジェネレータを使用した際にJavaScriptを使用しないようにする。

**参考**

- [Make Webpacker the default JavaScript compiler for Rails 6 by dhh · Pull Request #33079 · rails/rails](https://github.com/rails/rails/pull/33079)

### MySQLでutf8mb4文字セットがデフォルトに

絵文字などをサポートするために、MySQLのデータベースで使用する標準の文字セットが`utf8`から`utf8mb4`に変更になりました。これにより、以下のような変更が行われています。

- MySQL 5.5.8、MariaDB 10.2.2以上をサポートするバージョンに変更。
- InnoDBのインデックスの最大キー長を3072バイトに
- Active Recordモデルのユニットテストで使用していた`utf8_unicode_ci`のコレーションではなくデフォルトのコレーションを使用するように。

**参考**

- [Use utf8mb4 character set by default for MySQL database by yahonda · Pull Request #33608 · rails/rails](https://github.com/rails/rails/pull/33608)

### Active Jobでenqueueが失敗した際に`false`を返すように

Active Jobで、`before_enqueue`などのコールバック内でジョブが異常終了した場合は`enqueue`でジョブではなく`false`を返すようになりました。これにより、ジョブが正しくキューに入れられたか失敗したかどうか知ることができるようになりました。

```ruby
class AbortBeforeEnqueueJob < ActiveJob::Base
  before_enqueue { throw(:abort) }

  def perform
  ...
end

AbortBeforeEnqueueJob.new.enqueue
# before
# => #<AbortBeforeEnqueueJob:0xXXXXXX @arguments=[], @job_id="0d5ef7b9-d32b-4c83-b8cf-44ec31e9d6d9" ...
# after
=> false
```

**参考**

- [Make AJ::Base#enqueue return false if the job wasn't enqueued by kirs · Pull Request #33992 · rails/rails](https://github.com/rails/rails/pull/33992)

### `config.filter_parameters`が`#inspect`メソッドでも有効に

これまでも`Rails.application.config.filter_parameters`に属性名を記述することで、機密情報（パスワードやクレジットカード番号など）をログに出力する際は`[FILTERED]`のようにフィルタリングすることができましたが、これが、`inspect`メソッドの呼び出しの場合も有効になるようになりました。

```ruby
Rails.application.config.filter_parameters += [:credit_card_number]
Account.last.insepct # => #<Account id: 123, credit_card_number: [FILTERED] ...>
```

**参考**

- [Add `inspection_masks` to make values of sensitive database columns w… by piecehealth · Pull Request #33756 · rails/rails](https://github.com/rails/rails/pull/33756)

## まとめ

Rails 6は、Action TextやAction Mailboxといった新しいフレームワークの導入とともに、複数データベースや並列テストなど、アプリケーションのスケーラビリティを高める新機能が含まれたバージョンになっています。フレームワークとしての完成度も更に高まっていますので、手持ちのアプリケーションをアップグレードしたり、新規に開発してみては、いかがでしょうか。

## 参考サイト

- [Rails 6.0: Action Mailbox, Action Text, Multiple DBs, Parallel Testing, Webpacker by default, and Zeitwerk](https://weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/)
- [Rails 6.0.0 beta1, and more | Riding Rails](https://weblog.rubyonrails.org/2019/1/22/this-week-in-rails-rails-6-0-0-beta1-and-more/)
- [Rails 6.0.0 beta2 released | Riding Rails](https://weblog.rubyonrails.org/2019/2/25/Rails-6-0-beta2-released/)
- [rails/rails: Ruby on Rails](https://github.com/rails/rails) - [6.0.0 Milestone](https://github.com/rails/rails/milestone/63)
- [News | Riding Rails](https://weblog.rubyonrails.org/news/)
- [RailsConf 2018 | The Future of Rails 6: Scalable by Default - Speaker Deck](https://speakerdeck.com/eileencodes/railsconf-2018-the-future-of-rails-6-scalable-by-default)