Ruby のテンプレートシステム Slim における早期リターンについて書く。
早期リターンとは
まずメソッド定義における早期リターンについて。
以下のようなメソッドがあったとする。
def write_items(items)
if items.empty?
puts "無い"
else
puts "全部で #{ items.length } 個あった"
items.each do |item|
puts "* #{ item }"
end
end
end
以下のように書き換えたほうが把握しやすい(個人の感想です)。
def write_items(items)
if items.empty?
puts "無い"
return
end
puts "全部で #{ items.length } 個あった"
items.each do |item|
puts "* #{ item }"
end
end
いや,こんな短いコードだと大して変わらないけど,else 節が長く複雑になると脳の負担が大きいからね。
こんなふうに,例外的な場合にとっとと話を終わらせちゃうのを早期リターン(early return)という。
これと同じように,Slim テンプレートでも,ある条件を満たす場合にとっとと話を終わらせたいことがある。
それをどう書くか。
お題
以下のようなコードを何とかしたい。
- if items.empty?
p 無い
- else
p 全部で #{ items.length } 個あった
ul
- items.each do |item|
li = item
いや,こんな単純なテンプレートだったら,このままでいい。
しかし,items が empty でなかったときのコードがもっと長く複雑だったなら,早期リターンしたいじゃない。
打ち切るだけなら,Slim テンプレート中でも return を使えばよいだけなのだが,<p>無い</p> をちゃんと表示してほしいんである。
「そこまでの内容をレンダリング結果とする」という仕組みが,どうも Slim には備わっていないようなのだ。
方案
Rails の場合
Rails では,(Slim に限らず)テンプレート中で output_buffer というメソッドで「そこまでの内容」が得られる。
そこで,以下のように書ける。
- if items.empty?
p 無い
- return output_buffer()
p 全部で #{ items.length } 個あった
ul
- items.each do |item|
li = item
ええと,こういうことやっていいのかな。
Rails API に output_buffer メソッドが見当たらないってことは,使っちゃいけないのかな(探し方が悪いだけかもしれないが)。
ちな,output_buffer() に () が付いているのは,同名のローカル変数があるため(次節参照)。() 無しだとローカル変数の参照になり,ありだとメソッド呼び出しになる。
バッファー変数を参照
Slim テンプレートの処理で,「そこまでの内容」はローカル変数に蓄えられている。
デフォルトではこの変数は _buf であるらしい。
よって以下のように書ける。
- if items.empty?
p 無い
- return _buf
p 全部で #{ items.length } 個あった
ul
- items.each do |item|
li = item
うーむ,しかしお行儀のよいコードとは言えまい。
バッファー変数はテンプレート中で使うことを想定していないと思う(知らんけど)。
また,この変数名は Slim の設定で変えることができる。
実際,いま Rails 7.2 で試したところ,output_buffer という名前の変数になっているようだった(先程見たメソッドと同名)。
だから,このテンプレートは普遍性にやや欠ける。何かのバージョンアップによって動かなくなるかもしれない。
lambda
以下はもう奇策といってよいかもしれない。
- if items.empty?
- return lambda{ |&b| b.call }.call
p 無い
p 全部で #{ items.length } 個あった
ul
- items.each do |item|
li = item
何をやっているのか分かりにくい。
書いた私にもよく分からん(笑)
キャプチャー
前節のやり方は,キャプチャーのメソッドがあればもっと素直に書ける。
capture ヘルパーを
def capture = yield
と定義すれば
- if items.empty?
- return capture
p 無い
p 全部で #{ items.length } 個あった
ul
- items.each do |item|
li = item
と書ける。
Rails というか ActionView には capture というメソッドがあるが,こちらはもっと複雑な定義になっている。
結局どれを
結局どれを使えばいいのだ。
Rails の場合は output_buffer() かなという気もするが,そもそもこれをテンプレート中に書いてよいのか分からん。
Rails で ActionView 備え付けの capture を使ってもできそうだが,試していない。
Rails じゃない場合は自前 capture だろうか(メソッド名はともかく)。