口頭補足
Railsの高い生産性とモデルの肥大化の機序を理解すること、既存コードから操作を発見し、モデリングする方法を理解することがゴール。
Railsで一通りの機能を実装でき、モデルの肥大化とその解決策について議論したことがある方を想定する。
口頭補足
メールの検証と確認メールの送信予約をUserに置く。UserEmailを作ってメールアドレスの値と確認状態をUserから分離する設計もできるが、この例ではUserにまとめて説明する。
口頭補足
form_withはモデル名と新規・保存済みの状態から送信先を推定する。ここでは新規UserなのでPOST /usersになり、resourcesが生成したルーティングにマッチする。
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%221umC9DkSM2zcDom8CygX%22%7D
口頭補足
ここまでの内容を抽象化すると、Railsでは、scaffoldで生成したモデルにバリデーションやコールバックを追加するだけで、更新系のユースケースを実現できる、と言える。これがRailsの高い生産性の正体。
それを支える要因は二つ。
1. アプリケーションの構造があらかじめ用意されており、設計判断が減らせること。これにより、データモデリングと必要な振る舞いの実装に集中できる。
2. scaffoldとバリデーション・コールバックを利用して、記述するコード量を減らせること
口頭補足
Rails WorldのKeynoteでの「書かずに済んだコードに興奮していた」という言葉。今の例でも、Railsに任せられる部分が多い。
口頭補足
buildした紐付け情報はUserを保存すると一緒に保存される。関連の検証もこの経路で実行されるため、uidの検証はUserIdentityに任せられる。
口頭補足
on: :google_signupで確認日時の存在を検証し、unless: :email_verified_atで確認メールのコールバックを実行しない。確認済みの値を受け取ったときだけ日時を設定するため、この条件でGoogle側の確認状態を検証できる。
口頭補足
OmniAuthでGoogle認証のリクエストとレスポンスを扱う例。このfrom_omniauthが返すUserが保存済みかどうかで成功と失敗を分けている。この例では省いている、既存ユーザーを取得できた場合も成功扱いになる。
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%22DgWlHgV3BffLBHDclJP7%22%7D
口頭補足
過去のKaigi on Railsでは、フォームを表すオブジェクトに固有の処理を担わせる方法や、行為を表すモデルに自身の記録と関連処理を担わせる方法が議論された。イベントエンティティは業務上の出来事を記録するエンティティという意味で用いる。
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%22jHk9XW5TJDwDVSJHRNqv%22%7D
口頭補足
操作そのものを表現するモデルを導入する。以降はoperation modelと呼ぶ。
口頭補足
EmailSignUpのsaveはUserを保存したあとに確認メールの送信を予約する。もともとunless: :email_verified_atで実行条件を指定していたコールバックに相当する振る舞いが、この操作のモデルに移っている。
口頭補足
GoogleSignUpはメールアドレスが確認済みかを検証し、紐付け情報をbuildしてUserを保存する。on: :google_signup付きのバリデーションで実現していた確認済みかどうかの検証が、このモデルに対応する。
口頭補足
Userからはサインアップ固有のコールバックや実行条件がなくなる。この例ではメールの形式と未登録の検証、確認用トークンをUserに残している。操作のモデルへ何でも移すという意味ではない。
口頭補足
Userに残した検証は操作モデルからも利用する。
Userの検証に失敗したら、必要な属性のエラーを操作モデルのエラーとして取り込む。
ActiveModel::Errors#import APIはRails 6.1より利用可能
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%22kRmInHfIsOCptKvRygoD%22%7D
口頭補足
操作を表現するテーブルと対応しないモデルの導入により、既存モデルへユースケース固有の振る舞いが蓄積する、Railsのアプローチの欠点に対処できた。
一方で、URLで表されるリソースとモデルへの操作を対応させ、モデルの作成操作を中心にユースケースを構成する、Railsのアプローチの利点は維持できた。
口頭補足
EmailSignUpをActiveRecordのモデルにする。belongs_toの新規Userは操作モデルとともに保存され、作成のコミット後に確認メールを予約する。紹介者への参照も記録できる。二つの操作モデルをSTIで同じテーブルに対応させる設計も可能だが、ここでは割愛する。
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%22-GD7zlmD5HF45cgQk2EL%22%7D
https://app.diagrams.net/#G16Y4CRwATXYsIf7yPwDiFxnKkzJeN9gXq#%7B%22pageId%22%3A%22-GD7zlmD5HF45cgQk2EL%22%7D
口頭補足
既存のコードから、モデル化すべき操作をどう発見するか。手書きしたコードにも、AIコーディングエージェントが生成したコードにも同じ問いを向けられる。
口頭補足
ガード節の例はEric EvansのDDD本から、他は発表者の経験に基づく。こうした実装なら必ず操作モデルにする、という判定基準ではない。
口頭補足
サインアップ操作をEmailSignUpとして抽出し、作成後の確認メール送信予約をそこへ集約する。Userには形式と未登録の検証を残す。
ここで表示しているのは前のパートと同じ操作モデルで、本パートでは既存のUserからそれを発見する判断の過程を示した。