SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Railsの新機能を知ろう!

Rails 7.1の注目新機能──Dockerfileの自動生成や非同期クエリ、複合主キーなど──を使いこなす!

Railsの新機能を知ろう! 第1回

Active Recordのサポート拡張

 Active Recordでは、非同期クエリのためのメソッド、属性値の正規化、複合主キーなどが新たにサポートされました。

非同期クエリのサポート拡張

 Active Recordには、load_asyncという非同期実行を指示するメソッドがありました。countなどのメソッドにチェインして呼び出すことで非同期実行するものですが、Rails 7.1では非同期メソッドが大きく拡張され、メソッド単独での非同期実行が可能になりました。以下のメソッドが利用できます。

async_count、async_sum、async_minimum、async_maximum、async_average、async_pluck、async_pick、async_ids、async_find_by_sql、async_count_by_sql

 例えばasync_countメソッドは、一致するレコード数を返すcountメソッドの非同期版です。例えばname属性とbirth属性を持つUserモデルに対して、以下のように使用します。

% rails c
> User.create!(name: 'Nao', birth: 1980)
> User.create!(name: 'Shino', birth: 1985)

# 同期版
> count = User.where('birth >= 1980').count
=> 2

# 非同期版
> promise = User.where('birth >= 1980').async_count
=> #<ActiveRecord::Promise status=complete>
> promise.value
=> 2

 非同期版では、async_countメソッドの戻り値はActiveRecord::Promiseとなり、処理終了後にPromiseオブジェクトを通じて結果を受け取ります。これらのメソッドを意識して使うことで、単純なクエリでもそれなりに時間のかかる処理を並列化できるので、パフォーマンスの向上が期待できます。

normalizesによる属性値の正規化

 Rails 7.1では、ActiveRecord::Base.normalizesが使えるようになり、属性値に対して正規化を宣言できるようになりました。正規化とは、値を望ましい形に整えることで、ユーザー入力のサニタイズや書式の統一で役立つ機能です(リレーショナルモデルにおけるいわゆる正規化とは意味が異なります)。正規化を属性値に指定することで、値はデータベーステーブルに正規化された形で永続化され、正規化されていないパラメーターでの検索も可能になります。

 属性への正規化は、以下のリストのように指定します。この場合、email属性は先頭と末尾の空白文字(\t\n\f\v)は除去され、英小文字に変換されます。normalizesを指定された属性はクエリ文字列でも正規化されるので、事前の整形が不要になり便利です。

リスト:normalizes/app/models/user.rb
class User < ActiveRecord::Base
    normalizes :email, with: -> email { email.strip.downcase }
end

 以下は、コンソールでの操作例です。空白文字を含んだり、英大文字で構成されたメールアドレスも、永続化された値もクエリの値も期待する形に正規化されることを確認できます。

% rails c
> user = User.create!(name: 'Nao', email: "\tNAO@NAOSAN.JP\n")
> user.email
=> "nao@naosan.jp"
> User.where(email: "\tNAO@NAOSAN.JP\n").count
=> 1

複合主キーのサポート

 Rails 7.1では、従来の1カラムによる主キーに加え、複数のカラムからレコードを一意に指定する複合主キー(composite primary key)が使えるようになりました。例えば、2つの外部キー(costomer_idとproduct_id)持つ注文テーブル(orders)が、これらのキーで各レコードを識別するとします。このとき、customer_idとproduct_idが複合主キーとなります。

 複合主キーをテーブルに使用するには、モデル作成後のマイグレーションファイル内で、create_tableメソッドのprimary_keyオプションに主キーとするフィールドの配列を指定します。マイグレーション時には、primary_keyが指定されていることが分かります。

リスト:composite_pk/db/migrate/20231113032225_create_orders.rb
class CreateOrders < ActiveRecord::Migration[7.1]
  def change
    create_table :orders, primary_key: [:customer_id, :product_id] do |t|
      t.integer :customer_id
      t.integer :product_id
      t.integer :quantity
    end
  end
end
% rails db:migrate
== 20231113032225 CreateOrders: migrating =====================================
-- create_table(:orders, {:primary_key=>[:customer_id, :product_id]})
   -> 0.0029s
== 20231113032225 CreateOrders: migrated (0.0029s) ============================

 findメソッドによる検索では、同じく配列で複合主キーを指定します。配列の配列とすることで、いずれかの複合主キーに一致するものという条件で検索できます。

% rails c
> Order.create!(customer_id: 1, product_id: 2, quantity: 10)
> Order.create!(customer_id: 2, product_id: 4, quantity: 20)
> order = Order.find([2, 4])
> order.quantity
=> 20

 複合主キーは一見便利そうですが、テーブルの頻繁な更新ではインデックスの構築などのオーバーヘッドが大きくなります。参照が主体であるテーブルに限定するか、そもそも複合主キーが必要なのか(テーブルの正規化で解決できないか)、見定めて使う必要があるでしょう。

ワンタイムトークン生成と検証メソッドの追加

 Rails 7.1では、ワンタイムトークン生成とその検証のためのActiveRecord::TokenForクラスのメソッドが使用できるようになりました。これらのメソッドは、ユーザーの情報を保持するようなクラスがパスワードを扱う場合に、認証のために一時的に利用するトークン生成や検証の機能をサポートします。主にパスワードリセットなどで便利に使用できます。以降は、has_secure_passwordメソッドによるパスワード管理機能を実装したモデルにおける、トークン生成と検証の例です。

 has_secure_passwordメソッドは、BCrypt(Blowfish暗号に基づいて作られたパスワードハッシュ関数)に基づいた認証の仕組みをモデルに導入します。なお、has_secure_passwordメソッドの利用にはbcrypt GEMが必要で、テーブルにはハッシュ化されたパスワードの永続化のためのpassword_digestフィールドが必要となります。

 bcrypt GEMの利用には、Gemfileに以下の行を追加し、bundleコマンドを実行します。

リスト:gen_token/Gemfile
gem "bcrypt"

 テーブルは、例えばUserモデルをpassword_digestフィールドを含めて以下のように作成します。

% rails g model user name:text password_digest:text

 has_secure_passwordメソッドによってモデルに追加される属性、メソッド、検証機能は以下の通りです。

  • 属性:password(パスワード)、password_confirmation(検証用パスワード)、password_salt(パスワードから計算されるSALT値《ハッシュ化の際に用いられるランダムな文字列》)
  • メソッド:authenticate(パスワード検証用。引数とpasswordが一致すればtrueを返す)
  • 検証機能:モデル生成時にパスワード(password)と確認パスワード(password_confirmation)が必要で、かつ一致する必要がある(一致しないとnil)

 以下のリストは、has_secure_passwordメソッドによるパスワード管理機能を実装したクラスで、パスワードリセットのためのトークンを生成する例です。

リスト:gen_token/app/models/user.rb
class User < ActiveRecord::Base
    has_secure_password

    generates_token_for :password_reset, expires_in: 10.minutes do
        password_salt&.last(10)
    end
end

 ここでは、generates_token_forメソッドを以下の指定で呼び出しています。

  • 第1引数(purpose)にpassword_resetを指定し目的をパスワードリセットとする
  • expires_inオプションで10分後に破棄する(既定では有効期限なし)

 generates_token_forメソッドはあくまで目的ごとのトークンの作成方法を定義するのみで、ここではトークンそのものを作成しているわけではありません。具体的には、SALT値であるpassword_saltの末尾10文字を抜き出した値をトークンに埋め込むことを指定しているだけです。

 実際にトークンを生成するのは、generate_token_forメソッド(generateは単数形)です。このときトークンは、generates_token_forメソッドの指定でSALT値の一部、そしてモデルのid属性、有効期限、目的を保持するJSONデータをBase64エンコーディングしたものに、ActiveSupport::MessageVerifierによる署名を付加して生成されます。署名の付加により改ざんが防止されるので、外部へ公開することが可能になるわけです。なお、トークンはパスワードのダイジェストと異なり永続化されません。モデルのクラス変数として保持されるだけの、一時的なデータです。

 以下に、具体的な例を示します。

% rails c
# パスワードを指定してユーザーを作成する
> User.create!(name: 'Nao', password: 'password', password_confirmation: 'password')
# 最初に見つかるユーザーを取得、パスワードリセット目的のトークンを生成する
> user = User.first
> token = user.generate_token_for(:password_reset)
=> "eyJfcmFpbHMiOnsiZGF0YSI6WzEsIjFyVUdhUmQxay4iXSwiZXhwIjoiMjAyMy0xOToxOC4zMT..."

# パスワードリセット目的の指定トークンを持つユーザーを検索する(userが返る)
> User.find_by_token_for(:password_reset, token)

# ユーザーのパスワードを更新して、再度トークンを指定してユーザーを検索する
# (トークンは無効になっているので検索は失敗してnilが返る)
> user.update!(password: "new_password")
> User.find_by_token_for(:password_reset, token)
=> nil

 既述の通り、generate_token_forメソッドで実際にトークンが生成されます。このトークンを用いてfind_by_token_forメソッドでレコードを検索できます。パスワードを変更するとトークンは無効になるので、そのトークンを持つレコードは存在しないとしてfind_by_token_forメソッドからはnilが返ります。

 ユーザーがパスワードリセットしたいときなどに、署名や有効期限のあるトークンを使って認証できるので、安全で利便性の高いパスワードの運用が可能になる機能と言えます。

まとめ

 今回は、Ruby on Rails 7.1の新機能のうち、DockerサポートやActive Recordにおける機能強化について紹介しました。

 次回は、Active Jobの新機能、新しいデータベースアダプターとJavaScriptランタイム環境をはじめとする主要な機能強化について紹介します。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Railsの新機能を知ろう!連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/18726 2023/12/21 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー