はじめに
ここ数年は毎年リリースされてきたRuby on Rails (以下Rails)の新しいバージョンですが、今回はメジャーバージョンアップグレードの6.0(以下Rails 6)となり、2019年8月にリリースされました。
Rails 6では、Action TextとAction Mailboxの新しい2つのフレームワークの導入を始め、複数データベースや並列テストへの対応など、メジャーバージョンアップグレードにふさわしい大きな機能追加が行われています。
本記事では、GitHubのRailsプロジェクトのIssuesやPull Requestsの内容をもとに、Rails 6の主要な新機能・機能追加・変更点の紹介を行います。
※ 以前のバージョンのRailsの主要な新機能・機能追加・変更点については以下を参照してください。
- 今から知っておきたいRails 5の新機能・変更点 - Qiita
- ReactやwebpackもサポートしたRails 5.1の新機能・変更点 - Qiita
- Ruby on Rails 5.2の新機能(Active Storage, Content Security Policyなど) - Qiita
注意点
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
- Require Ruby 2.5 for Rails 6. by kaspth · Pull Request #34754 · rails/rails
新機能
Action Text
Action Textは、Railsアプリケーション内で画像やファイルを含むリッチテキストの編集を可能にするフレームワークです。Action Text自体はTrix editorというJavaScriptのライブラリと、Active Storageを利用しています。
Action Textを使用するには、モデルでhas_rich_text
をリッチテキストを使用する属性(今回の例ではcontent
)に対して指定を行います。
class Message < ApplicationRecord
has_rich_text :content
...
フォームでリッチテキストの編集フィールドを使用するには、以下のようにrich_text_area
メソッドを使用します。
<%= form_with(model: message) do |form| %>
...
<div class="field">
<%= form.label :content %>
<%= form.rich_text_area :content %>
</div>
...
<% end %>
表示を行うには、以下のように元の属性(content
)を使用します。
<%= @message.content %>
コントローラ側でも、元の属性(content
)をStrong Parameterで指定するだけで使用できるようになっています。
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
- Introducing Action Text for Rails 6 | Riding Rails
Action Mailbox
Action MailboxはRailsアプリケーション内でメールの受信や表示、転送や破棄などのライフサイクル追跡などの処理を行うフレームワークです。
メールの受信は、SendGridなどの外部サービスからだけでなく、Postfixからも行うことができます。メールの受信自体は、RailsアプリケーションへのHTTP POSTによって行われます。(各サービスでのURLへの設定が必要になります。)
受信されたメールの内容(from:など)を元にルーティングが生成されます。
各メールはActionMailbox::InboundEmail
というモデルとして取り扱われ、アプリケーションでの表示や処理が可能になります。Active Storageを利用して、Amazon S3などのクラウドストレージに保存することも可能です。
各メールは受信後に指定日数(デフォルトで30日)で、ActionMailbox::IncinerationJob
によって焼却(破棄)が行われます。
参考
- rails/actionmailbox: Receive and process incoming emails in Rails
- Introducing Action Mailbox for Rails 6 | Riding Rails
複数データベース
Active Recordの機能拡張とアプリケーションの設定項目、Rakeタスクの追加により、複数のデータベースを利用することができるようになりました。
例えば、以下のように設定することで書き込み/読み込み/特定のデータの読み込みを行うデータベースを分けることが可能になります。
(development
の直下にspecification (spec)と呼ばれるprimary
、readonly
、animals
を追加しています。)
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と呼ばれます。)
class Venue < ApplicationRecord
connects_to database: { writing: :primary, reading: :readonly }
...
class AnimalsModel < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals, reading: :animals }
...
class Animal < AnimalsModel
...
以下のようにspecificationを指定したRakeタスクを使用することができます。
rails db:create:animals
rails db:migrate:animals
rails db:drop:animals
参考
- RailsConf 2018 | The Future of Rails 6: Scalable by Default - Speaker Deck
- GitHubのPull Request: Part 1, Part 2, Part 3, Part 4
並列テスト
デフォルトで並列でテストを行うようになりました。並列にするワーカーの数は以下のようにカスタマイズすることができ、それぞれ別の一時的なデータベースを使用します。また、プロセスで実行するか、スレッドで実行するかを設定することができます。(標準では、ワーカー数は2、別のプロセスで実行)
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
- Parallel testing by eileencodes · Pull Request #31900 · rails/rails
- Rails 5.1.5, parallel testing and more! | Riding Rails
新しいオートローダー(Zeitwerk)の導入
Zeitwerkと呼ばれる効率的でスレッドセーフなオートローダー(ファイルをクラスやモジュールとして読み込む仕組み)が導入されました。
Zeitwerkは実行時間が遅い$LOAD_PATH
内のファイルの走査を行わず、独自にプロジェクトツリーの走査を行い、ファイルの相対ファイル名を使用したクラスやモジュールの読み込みを行うことで高速化を実現しています。サブディレクトリは、親ディレクトリがモジュールになっているときのみに遅延してロードされます。
Zeitwerkの導入に伴って、アプリケーションの起動やファイルのリロードが改善されますが、従来のオートローダーの仕組みと異なるため、既存のアプリケーションなどでクラスの階層とファイルのパスが一致しない場合などは対応が必要になる可能性があります。
参考
- fxn/zeitwerk: Efficient and thread-safe code loader for Ruby
- Rails 6.0.0 beta2 released | Riding Rails
機能追加
DBへのバルクインサートの標準サポート
1つのSQL文で複数のレコードの挿入を行うバルクインサート(Bulk insert)が実装され、使用できるようになりました。
バルクインサートを実現するGemとしてactiverecord-importなどがこれまで使用されていましたが、Rails 6からは標準で、モデルに対してinsert_all
、upsert_all
メソッドを呼び出すと、レコードの一括挿入、一括更新が可能になります。
更新に関しては、unique_by
を指定して、どのカラムが一致すると上書きを行うかを指定することができます。
# 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)
参考
ActiveRecord::Relation#pick
DB上のテーブルの絞り込み条件や並べ替えを指定した際に、最初のレコードの特定のカラムの値を取得するためのメソッドであるActiveRecord::Relation#pick
が追加されました。
# 最後に作成されたユーザーの名前を取得
User.order(created_at: :desc).pick(:name)
# pickを使わない場合は以下のように書く必要がある
User.order(created_at: :desc).limit(1).pluck(:name).first
参考
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
メソッドを使用する際には、あらかじめユニーク制約を設定する必要があること、レコードの作成よりも参照が多い場合は処理自体が遅くなる可能性があること、また、レコードが作成できない場合の例外などの挙動の違いを把握しておく必要があります。
# 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
例外を発生させます。
参考
String#truncate_bytes
マルチバイト文字を含む文字列を指定したバイト数で正しく切り捨てを行うためのメソッドString#truncate_bytes
が追加されました。
# 20バイトで切り捨てを行う
"🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
=> "🔪🔪🔪🔪…"
参考
Action Cableのテストが可能に
Action Cableのテストを行うための機能(ActionCable::TestCase
クラス、assert_broadcast
メソッドなど)が追加されました。
参考
マイグレーションファイルを生成する対象となるデータベースを指定できるように
複数データベースに関連する変更点ですが、rails generate migration
を実行するときに--database
(短縮形は--db
)オプションを指定することで、マイグレーションファイルの生成対象となるデータベース(のspecification)を指定できるようになりました。オプションを指定しない場合は、config/database.yml
で設定されたprimaryのデータベースが使用されます。
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
- Refactor migrations_path command option to database by gmcgibbon · Pull Request #34021 · rails/rails
RDBMSを変更するタスクを追加
RDBMSを変更するdb:system:change
タスクが追加されました。
例えば、すでに既存のアプリケーションがSQLiteを使用している場合、以下のようにコマンドを実行することで、PostgreSQLを使用するように変更することができます。
bin/rails db:system:change --to=postgresql
参考
Active Jobのフックを追加
Active Jobで使用するenqueue_retry.active_job
、retry_stopped.active_job
、discard.active_job
のフックが追加されました。これによって、ジョブがリトライまたは破棄された際のログなどで、対象となるジョブの情報を把握することが容易になりました。
参考
ActiveRecord::Relation#where
の条件で終端なしのRange
を使用できるように
Ruby 2.6で終端なしのRangeが使用できるようになったので、これに合わせてActiveRecord::Relation#where
でも引数の対応が行われ、終端なしのRangeを使用できるようになりました。
features.where(awesomeness: 10..) # 10..は10..Float::INFINITYと同じ
参考
変更点
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を使用しないようにする。
参考
MySQLでutf8mb4文字セットがデフォルトに
絵文字などをサポートするために、MySQLのデータベースで使用する標準の文字セットがutf8
からutf8mb4
に変更になりました。これにより、以下のような変更が行われています。
- MySQL 5.5.8、MariaDB 10.2.2以上をサポートするバージョンに変更。
- InnoDBのインデックスの最大キー長を3072バイトに
- Active Recordモデルのユニットテストで使用していた
utf8_unicode_ci
のコレーションではなくデフォルトのコレーションを使用するように。
参考
Active Jobでenqueueが失敗した際にfalse
を返すように
Active Jobで、before_enqueue
などのコールバック内でジョブが異常終了した場合はenqueue
でジョブではなくfalse
を返すようになりました。これにより、ジョブが正しくキューに入れられたか失敗したかどうか知ることができるようになりました。
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
参考
config.filter_parameters
が#inspect
メソッドでも有効に
これまでもRails.application.config.filter_parameters
に属性名を記述することで、機密情報(パスワードやクレジットカード番号など)をログに出力する際は[FILTERED]
のようにフィルタリングすることができましたが、これが、inspect
メソッドの呼び出しの場合も有効になるようになりました。
Rails.application.config.filter_parameters += [:credit_card_number]
Account.last.insepct # => #<Account id: 123, credit_card_number: [FILTERED] ...>
参考
まとめ
Rails 6は、Action TextやAction Mailboxといった新しいフレームワークの導入とともに、複数データベースや並列テストなど、アプリケーションのスケーラビリティを高める新機能が含まれたバージョンになっています。フレームワークとしての完成度も更に高まっていますので、手持ちのアプリケーションをアップグレードしたり、新規に開発してみては、いかがでしょうか。
参考サイト
- Rails 6.0: Action Mailbox, Action Text, Multiple DBs, Parallel Testing, Webpacker by default, and Zeitwerk
- Rails 6.0.0 beta1, and more | Riding Rails
- Rails 6.0.0 beta2 released | Riding Rails
- rails/rails: Ruby on Rails - 6.0.0 Milestone
- News | Riding Rails
- RailsConf 2018 | The Future of Rails 6: Scalable by Default - Speaker Deck