with AI / Railsでviewを書くときにcontrollerからviewにどう値を渡すか?
みなさんこんにちは
最近ではRailsはAPIモードでだけ利用し、viewはJSで書くなんて人も多いと思いますが今回は未だにRailsでviewを書く僕みたいな人たちに向けた記事です
with AIと書いてますが、個人的にはもうずっとこのスタイルでやっているのでアレルギーがなければ通常の開発でも使えると思います
普通のやり方
# controller
def show
@user = User.find(params[:id])
end
# view
<div><%= @user.name %></div>
<div><%= current_user.name %></div>
多分みなさんが1兆回書いたやり方だと思います
なぜこれがAI向きではないのか?
本題です
AIは 「暗黙の知」 に非常に弱いです
例えば、上記のcontrollerは実は < UserController
していて、そこのbefore_action
で@user
をセットしている~なんてのはRailsではあるあるだと思います
それ以外でも例えばcurrent_user
だったり、application_controller
で設定しているhelper類だったり、そういったそのファイル単体では分からない 「非明示的メソッド」 があるとAIはそれを理解するために、元実装を追ったりどのようなメソッドが使えるのかを調査したり非常にコストが上がります
それは =コードを書くのに使えるコンテキスト(脳の容量)が減り、質が下がる という事を意味します
本当に理想的な状態というのは 1つのファイルに全てが明示され、その1つのファイルだけを考えれば答えが出せる状態 です。もちろん現代開発においてapplication_controller
に1万行詰め込むみたいな事はしませんし、適切にファイル分割するのが間違いなく正しいですし人間の可読性を犠牲にする事になります
また、テストの観点でもviewから呼ばれる値が可変になるとAIは最適なテストを生成できません(このあたりはAPI modeで作っていると感じづらい)
だから我々は人間が読みやすい && AI フレンドリーを両立する必要があるわけです
💡 余談1:1ファイルに出来るだけ明示して暗黙知を減らすというアプローチは
人間の開発であっても非常に有効なので僕は常にこの視点で書くことを推奨しています
💡 余談2:xxx層じゃん!と思うかもですが、ファイル分離したら
またコンテキストの問題が発生するのでミニマムな解決策です
どうするの?
先に答えを書きます。僕の提案は以下です
# controller
def show
@context = {
user: User.find(params[:id]),
current_user: current_user
}
# ※ 厳密には、自前で拡張したOpenStructを使ってます / 非設定キーを叩くと例外を吐く OpenStruct
end
# view
<div><%= @context.user.name %></div>
<div><%= @context.current_user.name %></div>
シンプルに説明すると、viewに渡す値を一度@context
に全部入れてviewからは@context
だけを利用するというものになります
何が嬉しいの?
テスト容易性が上がる
「ビューで使われている値が明確」 になるため、テスト作成のコストが劇的に下がりますしAIがテストを生成するコストもその正確性も段違いに上がります
例えば、以下のように @contextの内容を直接検証 するだけでcontrollerのテストが完結します
# controller_test.rb
test "show assigns required context keys" do
get user_url(1)
context = assigns(:context)
# ビューで使うキーが存在するか検証
assert_respond_to context, :user # userメソッドがあるか
assert_respond_to context, :current_user
# 型チェックで十分(値の内容はビューが責任を持つ)
assert_kind_of User, context.user
assert_kind_of User, context.current_user
end
💡 余談:APIモードだとレスポンスJSONの検証で同様のテストが可能ですが、
通常のRailsビューでは「どの値がビューで使われているか」が暗黙的でした。
これが明示化されるだけでテスト戦略が格段に洗練されます。
AIのコンテキストを節約
- # 従来のやり方(AIの苦手パターン)
- 「@userはどこで定義されてる?
- → before_actionか? application_controllerか?
- → そもそもcurrent_userはヘルパー? Devise?
- → どれを参考にコード生成すれば…?」
+ # @context方式(AIフレンドリー)
+ 「@context.user と @context.current_user が使える。
+ それ以外は使えない。終わり。」
AIがコードを生成する際の思考プロセス がシンプル化されるため、
- バグ混入率が低下
- プロンプト設計が容易化(「@contextに含まれる値を使って~」と明示可能)
- レビュー時の「想定外のメソッド呼び出し」がほぼ発生しなくなる
.
他にも以下のように型を明示する事で更にシンプルに堅牢化する事もできます
# @context の構造をコメントで明示
def show
user = User.find(params[:id])
# @type [Hash{user: User, current_user: User, posts: Array<Post>}]
@context = {
user: user,
current_user: current_user,
posts: user.posts.published
}
end
将来的なAPI Mode移行が簡単になる
「ビュー用のデータ構造」と「APIレスポンスのデータ構造」を共通化 できるメリットがあります
例えば、先程の例であれば極論以下のようにするだけでAPIモードとして成立します
# controller
def show
@context = {
user: User.find(params[:id]),
current_user: current_user
}
respond_to do |format|
format.html # 通常のビュー
format.json { render json: @context } # いきなりAPI対応!
end
end
💡 注意:このまま行くとuser.emailなど出したくない情報まで全部ぶちこむので必ずfilterは必要です
デメリット
- 初期学習コスト: チームメンバーが新しいパターンに慣れる必要がある
- 既存コードの移行: 大規模プロジェクトでは段階的移行が必要
- キモい: 超キモい
まとめ
Railsなんも分からん
Discussion