ERB Hacks
seki@ruby-lang.org

ERBすごいぞ！という自慢をするので、わたしの承認欲求を満たして欲しい

ERB Hacks
seki@ruby-lang.org

ERB発掘

わたしについてdRuby/Rinda/ERBを書きました
ふだんはtoRubyというコミュニティにいます
ポケモンカードWCS2010栃木県代表です3

島根大の講義、楽しかった！

toRuby「とちぎRubyの勉強会」は来月200回
池澤さんの「Rubyの家庭教師をお願いしたいです」のメールが発端
池澤さんにお仕事を依頼すると、もれなくtoRubyの技術支援がついてくる！お得！

情報デザイン・ロゴ

4

おそらく、矢板市で一番Herokuを使ってると思う5

https://www.pokemon-card.com/ex/sv4/

今日の話
古代編
ERB予言とその答え合わせの話
未来編
ブロックを使ったhelperが実装できそうな話

6

ERBすごいぞ！という自慢をするので、わたしの承認欲求を満たして欲しい

古代編
ERB予言とその答え合わせ7

1999年頃のお話ですERB前史（1999年）
HTMLのエンティティ表記を置換するライブラリを書いてたら、eRubyというものを作っていることを教えてもらった
エンティティ表記ならWebオーサリングツールの邪魔しないだろう、と想像した（使ったことないけど）8

ruby-dev:5286 (1999-02-19) Re: htmlelem.rb (Re: HTML generator)

eRubyの仕様書
アルバイトの前田さん!!

ePerl

printと <%= .. %>が同じ!?

いま開発中なんだな〜

9ePerlの影響を強くうけているみたいだぞ作ってみたい制御構造を文書に埋め込めるのいいなー
標準出力っていうのはCGIに特化しすぎでは...

10

CGIのおさらい
CGIでレスポンスは$stdoutへの印字
このためCGIの後半はHTMLを印字するputsが並ぶeRubyのような仕様を思いつくのは自然かも

11

古のMacはまた違ったインターフェイスだったかも（AppleScriptで書けたと思う）

退屈なputs

puts %Q{<!DOCTYPE html>}
puts %Q{<html lang="ja">}puts %Q{<head>}puts %Q{</head>}puts %Q{<body>}
puts %Q{<ul>}
ENV.each do |row|
puts %Q!<li>#{row[0]}</li>!
endputs %Q{</ul>}
puts %Q{</body>}
puts %Q{</html>}

12

古来のCGIはだいたいこんな感じhere document使うとマシ

puts %Q{<!DOCTYPE html>}
puts %Q{<html lang="ja">}puts %Q{<head>}puts %Q{</head>}puts %Q{<body>}
puts %Q{<ul>}
ENV.each do |row|
puts %Q!<li>#{row[0]}</li>!
endputs %Q{</ul>}
puts %Q{</body>}
puts %Q{</html>}

13

putsの回数は減らせた

puts <<EOS<!DOCTYPE html><html lang="ja"><head></head><body><ul>
EOS
ENV.each do |row|
puts %Q!<li>#{row[0]}</li>!
endputs <<EOS</ul></body></html>EOSputsからeRubyへ
puts <<EOS<!DOCTYPE html><html lang="ja"><head></head><body><ul>
EOS
ENV.each do |row|
puts %Q!<li>#{row[0]}</li>!
endputs <<EOS</ul></body></html>EOS

14<!DOCTYPE html><html lang="ja"><head></head><body><ul><% ENV.each do |row| %><li><%= row[0] %></li><% end %></ul></body></html>Stringの式展開では制御構造が書きにくい（書ける？）のだがeRubyならかんたんこの仕様はよくない気がする
CGIの最終段の処理に特化し過ぎてる
このスタイルは近いうちに変わる予感がする
ページの生成はこんなに単純ではなくなるぞ
CGIのモデルはサーバーぽいものへ変わるよ、きっと

15

古いUNIX, X11などでの開発経験からそう感じた、のかな...

単純じゃなくなるぞ予言
ページの一部をメソッドで生成したくなると思う<h2>report hoge</h2><ul><% result.each do |row| %><li><%= format_date(row.date) %></li><% end %></ul>
def format_date(date)date.strftime("%Y-%m-%d")end

16Helper?

単純じゃなくなるぞ予言
テンプレートを入れ子にしたくなる
共通のヘッダ、ナビケーション、フッタとコンテンツ<%= header %><%= navi %><%= content %><%= footer %>17

CGIのモデルは変わる 予言
CGIのプロセスのモデルは変わりそう
前処理のコストやリクエスト間の情報のやりとりなどから、なんらかの長生きなプロセスが必要になる
並行処理で$stdoutは問題になると思う

18

ERbLighteRubyのように動くERbと、予言に合わせた仕様のERbLightを用意した
印字ではなく文字列を組み立てるeRuby表記でRubyのアプリを書く(のを支援する）
変換器として働き、任意のbindingでのevalや、メソッド化を可能に

19

議論して揉めるべきだったがシャイなのでモノで表明した

Rubyを256倍使うための本
この仕様の可能性を誰か気がつくといいなあ
気づかれた！→網道編

20

たださんとartonさんがeRuby/dRubyを絶賛するすごい本

ERbLightからERBへ
ERBに改名されRubyの標準添付ライブラリになった21

四半世紀が過ぎた
予言、だいぶいい線いったのでは！
ERBが影響したライブラリが存在するのがうれしい
楽しいので自分用のライブラリは自分で書いてる

22

Merb・Railsは関係ある、と思っていいよね未来編
Railsのブロックを使ったhelperが実装できそうな話
10年前にあきらめたブロック問題

23

RWCに応募した後に気づいたネタがあったもとのネタ池澤プロダクツで多用されているテクニック
ERB#def_methodによるメソッド化
テンプレート内でのProcの利用

24↑toRubyの技術支援がついてるのでお得な池澤さんの「情報デザイン」のロゴ

Procの利用

Q
A

ユースケース：似たような設問が繰り返し出てくる
Q
アンケートのWebページを作るA
質問ごとに大きな表組みの回答欄がある
要素のidやformの名前はユニークにしたいQA

できればひとつのテンプレートの中で書きたいQA
QA
25↑矢板市北部で一番Herokuを使ってる「情報デザイン」のロゴ

実際のアンケートのようす質問ごとに表組の回答欄がある...と思うでしょ実は各セルごとに詳細の表がある

Procの利用Procで処理の一部を使いまわせる<% t = Proc.new do |arg| %><h3><%= arg %>回答欄</h3><table id="table-<%= arg %>">...
</table>ここを再利用したい<% end %><h3>質問1</h3>
最初の質問文だよ<% t.call("q1") %><h3>質問2</h3>
二つ目の質問だよ<% t.call("q2") %>29

ここで呼び出す<h3>質問1</h3>
最初の質問文だよ<h3>q1回答欄</h3><table id="table-q1">...
</table><h3>質問2</h3>
二つ目の質問だよ<h3>q2回答欄</h3><table id="table-q2">...
</table>Procの利用Procで処理の一部を使いまわせる<% t = Proc.new do |arg| %><h3><%= arg %>回答欄</h3><table id="table-<%= arg %>">...
</table><% end %><h3>質問1</h3>
最初の質問文だよ<% t.call("q1") %><h3>質問2</h3>
二つ目の質問だよ<% t.call("q2") %>30

パラメータ渡せるHTML片を組み立てる処理そのものがProcになるProcの利用Procで処理の一部を使いまわせる<% t = Proc.new do |arg| %><h3><%= arg %>回答欄</h3><table id="table-<%= arg %>">...
</table><% end %><h3>質問1</h3>
最初の質問文だよ<% t.call("q1") %><h3>質問2</h3><%= ... %>ではないので注意
二つ目の質問だよ<% t.call("q2") %>31

あれ？なんかこれ見たことある...

松田さんの2014年のトーク

32

Ruby Confね10年ほど前に相談されたRailsのテンプレートエンジンをERBに戻せる？
現在はErubisなのかな
技術的な問題はなさそうだったので試しました33

松田さんになにか相談されるなんてめったにないこと！

できませんでした

But gave up.
!

!

!

!

34

Because he didn't like this part in ActionView + Erubis
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/This code scans the template with the Regexp anddetects the Ruby block, but this kind of code could be
imperfect
So this is not acceptable as an ERB spec, said Sekisan.

Railsに正規表現でブロックの開始を検知して処理を切り替えるコードがあったこれが解けなかった
That is why this syntax is
not acceptable in ERB<%= form_for @article do |f| %>...
<% end %>35

Railsに正規表現でブロックの開始を検知して処理を切り替えるコードがあった<%= ... %>とブロックブロック付きメソッドを <%= ... %>に置くのが難しい<%= form_with do %>...
<% end %>36<%= ... %>とブロック
こうなっちゃう<%= form_with do %>...
<% end %>#coding:UTF-8_erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n\n".freeze; end ; _erbout.<< "\n".freeze; _erbout37

SyntaxError...

あれ？なんかできそうかも💡
RWCの応募したあとにProcの追試しながら考えてたら解けそうな気がしてきた38

https://gist.github.com/seki/610a42932a85209aaa33547ae983bbdf

なおしかた((...).to_s)の括弧がじゃま<%= form_with do %>...
<% end %>#coding:UTF-8_erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n\n".freeze; end ; _erbout.<< "\n".freeze; _erbout...

(( ... ).to_s) をなくせばよいかも？
→ 結果バッファクラスに移動させる

39

なおしかたRubyへの変換方法はカスタマイズできる<%= form_with do %>...
<% end %>一時変数の名前と初期化式の連結((...).to_s)をなくせばOKリテラルの連結#coding:UTF-8_erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n\n".freeze
結果の取得40; end ; _erbout.<< "\n".freeze; _erbout...

Rubyむずかしい
そんなに単純じゃなかった<<と括弧なしのメソッド呼び
出し_erbout << h("str")# OK_erbout << h "str"# SyntaxError_erbout.concat h "str"_erbout.concat form_with "arg" do |arg|...
end# OK but ....
# NG#
# concatにブロックが渡る_erbout = h "str"_erbout = form_with "arg" do |arg|...
end#<<でなくふつうのメソッド呼び出しにしても
#このブロックはconcatのブロック引数になる

代入なら大丈夫なのに...

41代入しながらメソッド呼べばよい?
+= でうまくいった_erbout += h "str"_erbout += form_with "arg" do |arg|
...
end

42

+=すばらしい# OK# OK

ERBOut

class ERBOut
Buffer = String # SafeBuffer if rails

結果バッファクラスの追加
いろんなトリックがある+で自分自身を返して+=をだます
captureのためのしかけ
自分自身を返す43

def initialize(s='')@str = Buffer.new(s)end
def to_s@str
end
def <<(other)@str << otherend

式の連結処理の一部をここへ移動

def +(other)@str << other.to_s
selfend
def capture(*arg, &block)save = @str
@str = Buffer.newyield(*arg)
return @str
ensure@str = saveend
end

ERBOut
結果バッファクラスの追加
いろんなトリックがある+で自分自身を返して+=をだます
captureのためのしかけ

class ERBOut
Buffer = String # SafeBuffer if rails
def initialize(s='')@str = Buffer.new(s)end
def to_s@str
end
def <<(other)@str << otherend
def +(other)@str << other.to_s
self
もとのバッファを退避end

一時バッファに差し替え

44

一時バッファをreturn
もとのバッファを復元

def capture(*arg, &block)save = @str
@str = Buffer.newyield(*arg)
return @str
ensure@str = saveendend

なんかできちゃった
中間結果のためのERBOutクラスも使ったらできた<%= form_with do %>...
<% end%>一時変数の名前と初期化

結果の取得

45

ここはブロックの内側だよ！

#coding:UTF-8_erbout = ERB::ERBOut.new; _erbout +="\n ...\n".freeze

form_with do; end ; _erbout <<; _erbout.to_s

式の連結+= がミソ"\n".freeze; _erbout <<https://gist.github.com/seki/610a42932a85209aaa33547ae983bbdf

captureの実装
ブロックのローカル変数をさわってcaptureのしかけを呼び出す
def capture(*arg, &block)
block.binding.local_variable_get(:_erbout).capture(*arg, &block)end
このブロックのbindingの _erbout
このブロックのbinding<% it = capture do %>...
<% end %>46

今後Rails環境でも動くことを確認できるといいなあ
めんどうなのでだれかやってくれるといいなあ

47（うごいてもうごかなくても沖縄で報告したい）

今日の話
古代編
ERB予言とその答え合わせの話
未来編
ブロックを使ったhelperが実装できそうな話

48

ERBもすごいぞ！という自慢でした