Help us understand the problem. What is going on with this article?

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

はじめに

ここ数年は毎年リリースされてきたRuby on Rails (以下Rails)の新しいバージョンですが、今回はメジャーバージョンアップグレードの6.0(以下Rails 6)となり、2019年8月にリリースされました。

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

本記事では、GitHubのRailsプロジェクトのIssuesPull Requestsの内容をもとに、Rails 6の主要な新機能・機能追加・変更点の紹介を行います。

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

注意点

Rubyのバージョン

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

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

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

参考

新機能

Action Text

Action Textは、Railsアプリケーション内で画像やファイルを含むリッチテキストの編集を可能にするフレームワークです。Action Text自体はTrix editorというJavaScriptのライブラリと、Active Storageを利用しています。

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

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

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

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)を使用します。

<%= @message.content %>

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

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

参考

Action Mailbox

Action MailboxはRailsアプリケーション内でメールの受信や表示、転送や破棄などのライフサイクル追跡などの処理を行うフレームワークです。

メールの受信は、SendGridなどの外部サービスからだけでなく、Postfixからも行うことができます。メールの受信自体は、RailsアプリケーションへのHTTP POSTによって行われます。(各サービスでのURLへの設定が必要になります。)

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

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

参考

複数データベース

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

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

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"
...

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

app/models/venue.rb
class Venue < ApplicationRecord
  connects_to database: { writing: :primary, reading: :readonly }
  ...
app/models/animals_model.rb
class AnimalsModel < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :animals, reading: :animals }
  ...
app/models/animal.rb
class Animal < AnimalsModel
  ...

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

rails db:create:animals
rails db:migrate:animals
rails db:drop:animals

参考

並列テスト

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

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

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

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

参考

新しいオートローダー(Zeitwerk)の導入

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

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

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

参考

機能追加

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

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

バルクインサートを実現するGemとしてactiverecord-importなどがこれまで使用されていましたが、Rails 6からは標準で、モデルに対してinsert_allupsert_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

参考

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

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

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

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

参考

Active Jobのフックを追加

Active Jobで使用するenqueue_retry.active_jobretry_stopped.active_jobdiscard.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といった新しいフレームワークの導入とともに、複数データベースや並列テストなど、アプリケーションのスケーラビリティを高める新機能が含まれたバージョンになっています。フレームワークとしての完成度も更に高まっていますので、手持ちのアプリケーションをアップグレードしたり、新規に開発してみては、いかがでしょうか。

参考サイト

ryohashimoto
A software engineer in Tokyo. Ruby on Rails contributor. React、Go、Elasticsearchを使ったWebサービスの開発をやっています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした