Slide 1

Slide 1 text

ERB, ancient and future [email protected] Beyond Journey's End

Slide 2

Slide 2 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 2

Slide 3

Slide 3 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 40年くらいプログラマだよ 3

Slide 4

Slide 4 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner Complex embedded systems with concurrency and GUI 4 Gallstones

Slide 5

Slide 5 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner I wrote them in 1999-2000 5

Slide 6

Slide 6 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner I wrote them in 1999-2000 6

Slide 7

Slide 7 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 7 Makoto Inoue Web Developer @ New Bamboo (UK) Translator of “The dRuby Book” Rails is A Follower what we can learn from dRuby’s metaprogramming magic Masatoshi SEKI Makoto Inoue

Slide 8

Slide 8 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 8 Big Bird. (scaling twitter) Rinda • Shared Queue (TupleSpace) • Built with DRb • RingyDingy makes it stupid easy • See Eric Hodel’s documentation • O(N) for take(). Sigh.

Slide 9

Slide 9 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 9

Slide 10

Slide 10 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner 第205回toRubyは2024-06-05 10 情報デザインのロゴ

Slide 11

Slide 11 text

About me Masatoshi Seki Ruby Core Committer (dRuby, Rinda, ERB) toRuby / Tochigi Ruby Meetup Pokémon TCG, WCS 2010 Tochigi Pref. winner RubyKaigi 2022, 2023 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

13 https://tcg.pokemon.com/en-us/expansions/paradox-rift/

Slide 14

Slide 14 text

Agenda ERB conjecture Rails-compatible ERB 14

Slide 15

Slide 15 text

Ancient ERB conjecture ERB予想 15 1999

Slide 16

Slide 16 text

Before ERB When I posted a template engine that replaces &entity;, Shugo told me about his plans for eRuby. ruby-dev:5286 (1999-02-19) Re: htmlelem.rb (Re: HTML generator) 16 1999

Slide 17

Slide 17 text

eRuby specs ePerlの影響を強くうけているみたいだぞ 17 print ≒ <%= .. %> !? I'm not the one implementing it (Tsundere) ePerl! ? Shugo CEO was a part-time worker at the time. 1999

Slide 18

Slide 18 text

ePerl !? e is for embedded. 文書にPerlが埋め込まれてる感じみたい 18 ... !> !? mod_perl !!! 1999

Slide 19

Slide 19 text

I'll write it embedding control structures 😊 CGI-oriented 😭 CGIに寄せすぎでは? 19 1999

Slide 20

Slide 20 text

Hello, World. 20 Hello, World. print "Hello, World.\n"

Slide 21

Slide 21 text

Hello, World. 21

<% print "Hello, World!" %> <%= "Hello, World! %>

print "

\n" print "Hello, World!" print "Hello, World!" print "

\n"

Slide 22

Slide 22 text

control structure 22
    <% ENV.each do |row| %>
  • <%= row[0] %>
  • <% end %>
print "
    \n" ENV.each do |row| print "
  • "; print row[0]; print "
  • \n" end print "
\n"

Slide 23

Slide 23 text

Remember CGI? $stdout → HTTP response CGI・おぼえていますか 23 puts %Q{Content-Type: text/html} puts puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{
    } ENV.each do |row| puts %Q!
  • #{row[0]}
  • ! end puts %Q{
} puts %Q{} puts %Q{} Content-Type: text/html
  • TERM_PROGRAM
  • ...

Slide 24

Slide 24 text

Remember CGI? $stdout → HTTP response CGI・おぼえていますか 24 puts %Q{Content-Type: text/html} puts puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{
    } ENV.each do |row| puts %Q!
  • #{row[0]}
  • ! end puts %Q{
} puts %Q{} puts %Q{} Content-Type: text/html
  • TERM_PROGRAM
  • ...
puts puts puts

Slide 25

Slide 25 text

Rewrite puts to eRuby Stringの式展開では制御構造が書きにくい(書ける?)のだがeRubyならかんたん 25 puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{} puts %Q{
    } ENV.each do |row| puts %Q!
  • #{row[0]}
  • ! end puts %Q{
} puts %Q{} puts %Q{}
    <% ENV.each do |row| %>
  • <%= row[0] %>
  • <% end %>

Slide 26

Slide 26 text

This specification is not good CGI programming style will change in the near future Page generation becomes more complex Will change to something more server-like 26 1999

Slide 27

Slide 27 text

becomes more complex Part of the page will be generated by a method Helper? 27

report hoge

    <% result.each do |row| %>
  • <%= format_date(row.date) %>
  • <% end %>
def format_date(date) date.strftime("%Y-%m-%d") end ERB conjecture 1999

Slide 28

Slide 28 text

becomes more complex nest common header, navigation, footer and content 標準出力に出すよりも連結した方がよいのでは? 28 ERB conjecture <%= header %> <%= navi %> <%= content %> <%= footer %> 1999

Slide 29

Slide 29 text

CGI programming model will change A long-lived server is required due to preprocessing costs and information exchange between requests. e.g. dRuby + ERB Concurrency and $stdout will conflict 29 ERB conjecture 1999

Slide 30

Slide 30 text

ERb and ERbLight ERb - eRuby's spec, like a command ERbLight - eRuby w/o $stdout, String concatenation a library, build the page using String instead of puts() Support writing Ruby applications using eRuby notation 30 1999

Slide 31

Slide 31 text

Ruby x 256 book Legendary hacker explains the power of this specification 2001, ASCII たださんとartonさんがeRuby/dRubyを絶賛するすごい本 31 2001

Slide 32

Slide 32 text

renamed ERbLight to ERB And ERB was included in Ruby's standard library. ERb is obsolete ERb is obsolete 32 2003

Slide 33

Slide 33 text

25 years have passed almost correct ! ERB-inspired libraries make me happy Merb, Rails... 自分用のライブラリは自分で書いてるけど、そういうものがあるのはうれしい 33 2024 ERB conjecture

Slide 34

Slide 34 text

Usage ERB.new, ERB#result 34 >> ERB.new('Hello World').result => "Hello World" >> ERB.new('1 + 1 = <%= 1 + 1 %>').result => "1 + 1 = 2"

Slide 35

Slide 35 text

ERB#src Translate eRuby to Ruby _erboutという変数に結果を連結する 35
    <% list.each do |row| %>
  • <%= row %>
  • <% end %>
>> puts erb.src #coding:UTF-8 _erbout = +''; _erbout.<< "
    \n".freeze ; list.each do |row| ; _erbout.<< "\n
  • ".freeze ; _erbout.<<(( row ).to_s); _erbout.<< "
  • \n".freeze ; end ; _erbout.<< "\n
\n".freeze ; _erbout

Slide 36

Slide 36 text

ERB#result with binding binding 36
    <% list.each do |row| %>
  • <%= row %>
  • <% end %>
>> erb = ERB.new(erb_src) >> list = [1, 'two', 3.0] >> erb.result(binding) => "
    \n\n
  • 1 li>\n\n
  • two
  • \n\n
  • 3.0 li>\n\n
\n"

Slide 37

Slide 37 text

ERB#def_method def_method(mod, methodname, fname='(ERB)') 37 class Foo def initialize(arg) @list = arg end ERB.new(< <% @list.each do |row| %>
  • <%= row %>
  • <% end %> EOS end Foo#bar instance var unpopular features

    Slide 38

    Slide 38 text

    ERB#def_method Avoid parsing the source multiple times Compose with small template objects Used only in Ikezawa's products 38 unpopular features

    Slide 39

    Slide 39 text

    Proc in template Case study: Create a survey page with similar questions repeatedly Each question has a large table with an answer column Element IDs and form names must be unique Write in one template ↑矢板市北部で一番Herokuを使ってる「情報デザイン」のロゴ 39 Q A Q A Q A Q A Q A

    Slide 40

    Slide 40 text

    Actual survey page

    Slide 41

    Slide 41 text

    Each question has an answer sheet... !?

    Slide 42

    Slide 42 text

    Each cell has detailed answer tables 10MB HTML

    Slide 43

    Slide 43 text

    Proc in template 43 <% t = Proc.new do |arg| %>

    Answer column for <%= arg %>

    ... <% end %>

    Question 1

    this is the first question <% t.call("q1") %>

    Question 2

    That's the second question <% t.call("q2") %>

    Question 1

    this is the first question

    Answer column for q1

    ...

    Question 2

    That's the second question

    Answer column for q2

    ... call

    Slide 44

    Slide 44 text

    Proc in template いやなことを思い出した 44 <% t = Proc.new do |arg| %>

    Answer column for <%= arg %>

    ... <% end %>

    Question 1

    this is the first question <% t.call("q1") %>

    Question 2

    That's the second question <% t.call("q2") %>

    Question 1

    this is the first question

    Answer column for q1

    ...

    Question 2

    That's the second question

    Answer column for q2

    ... call

    Slide 45

    Slide 45 text

    Future Rails-compatible ERB 45

    Slide 46

    Slide 46 text

    RubyConf 2014 https://speakerdeck.com/a_matsuda/template-engines-in-ruby 46 2014

    Slide 47

    Slide 47 text

    I tried やりましょう 47 I noticed this 2 years ago, ! And asked Seki-san if we could extend ERB to be "Rails compatible" before Ruby 2.0 release 2012

    Slide 48

    Slide 48 text

    and gave up できませんでした 48 But gave up. ! 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 and detects the Ruby block, but this kind of code could be imperfect ! So this is not acceptable as an ERB spec, said Seki- san.

    Slide 49

    Slide 49 text

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

    Slide 50

    Slide 50 text

    Maybe it's solved I noticed this while writing the slides for RWC2023. https://gist.github.com/seki/610a42932a85209aaa33547ae983bbdf 50 2023

    Slide 51

    Slide 51 text

    <%= ... %> to_s 51 <%= 1i ** 0.5 %> #coding:UTF-8 _erbout = +''; _erbout.<<(( 1i ** 0.5 ).to_s); _erbout.<< "\n".freeze ; _erbout

    Slide 52

    Slide 52 text

    <%= ... %> with block ブロック付きメソッドを <%= ... %>に置くのが難しい 52 <%= form_with do %> ... <% end %>

    Slide 53

    Slide 53 text

    <%= ... %> with block こうなっちゃう 53 <%= form_with do %> ... <% end %> #coding:UTF-8 _erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n ... \n".freeze ; end ; _erbout.<< "\n".freeze ; _erbout

    Slide 54

    Slide 54 text

    (( ... ).to_s) ((...).to_s)の括弧がじゃまなのでバッファクラスへ移動すればよい? 54 <%= form_with do %> ... <% end %> #coding:UTF-8 _erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n ... \n".freeze ; end ; _erbout.<< "\n".freeze ; _erbout

    Slide 55

    Slide 55 text

    Customizable Rubyへの変換方法はカスタマイズできる 55 <%= form_with do %> ... <% end %> #coding:UTF-8 _erbout = +''; _erbout.<<(( form_with do ).to_s); _erbout.<< "\n ... \n".freeze ; end ; _erbout.<< "\n".freeze ; _erbout

    Slide 56

    Slide 56 text

    Ruby is difficult Rubyむずかしい 56 _erbout << h("str") # OK _erbout << h "str" # SyntaxError <<と括弧なし のメソッド呼び 出し

    Slide 57

    Slide 57 text

    Ruby is difficult Rubyむずかしい 57 _erbout << h("str") # OK _erbout << h "str" # SyntaxError _erbout.concat h "str" # OK but .... _erbout.concat form_with "arg" do |arg| # NG ... # end # concatʹϒϩοΫ͕౉Δ <<と括弧なし のメソッド呼び 出し <<でなくふつうのメソッド呼び出しにしても このブロックはconcatのブロック引数になる

    Slide 58

    Slide 58 text

    Ruby is difficult Rubyむずかしい 58 _erbout << h("str") # OK _erbout << h "str" # SyntaxError _erbout = h "str" # _erbout = form_with "arg" do |arg| # ... end _erbout.concat h "str" # OK but .... _erbout.concat form_with "arg" do |arg| # NG ... # end # concatʹϒϩοΫ͕౉Δ 代入なら大丈夫なのに... <<と括弧なし のメソッド呼び 出し <<でなくふつうのメソッド呼び出しにしても このブロックはconcatのブロック引数になる

    Slide 59

    Slide 59 text

    Addition Asignment += works fine 代入しながらメソッド呼べばよい? 59 _erbout += h "str" # OK _erbout += form_with "arg" do |arg| # OK ... end

    Slide 60

    Slide 60 text

    ERBOut result buffer 60 class ERBOut Buffer = String # SafeBuffer if rails def initialize(s='') @str = Buffer.new(s) end def to_s @str end def <<(other) @str << other end def +(other) @str << other.to_s self end def capture(*arg, &block) save = @str @str = Buffer.new yield(*arg) return @str ensure @str = save end end

    Slide 61

    Slide 61 text

    ERBOut result buffer 61 class ERBOut Buffer = String # SafeBuffer if rails def initialize(s='') @str = Buffer.new(s) end def to_s @str end def <<(other) @str << other end def +(other) @str << other.to_s self end def capture(*arg, &block) save = @str @str = Buffer.new yield(*arg) return @str ensure @str = save end end

    Slide 62

    Slide 62 text

    ERBOut result buffer many tricks + 62 class ERBOut Buffer = String # SafeBuffer if rails def initialize(s='') @str = Buffer.new(s) end def to_s @str end def <<(other) @str << other end def +(other) @str << other.to_s self end def capture(*arg, &block) save = @str @str = Buffer.new yield(*arg) return @str ensure @str = save end end normalize and concatenate return itself

    Slide 63

    Slide 63 text

    ERBOut result buffer many tricks + capture 63 class ERBOut Buffer = String # SafeBuffer if rails def initialize(s='') @str = Buffer.new(s) end def to_s @str end def <<(other) @str << other end def +(other) @str << other.to_s self end def capture(*arg, &block) save = @str @str = Buffer.new yield(*arg) return @str ensure @str = save end end save restore call block and capture

    Slide 64

    Slide 64 text

    なんかできちゃった 64 <%= form_with do %> ... <% end %> #coding:UTF-8 _erbout = ERB::ERBOut.new; _erbout += form_with do ; _erbout << "\n ...\n".freeze ; end ; _erbout << "\n".freeze ; _erbout.to_s result buffer addition assignment

    Slide 65

    Slide 65 text

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

    Slide 66

    Slide 66 text

    I want someone to make it work in a Rails environment. I made no progress since last year 進捗ダメです 66

    Slide 67

    Slide 67 text

    Agenda ERB conjecture Rails-compatible ERB 67

    Slide 68

    Slide 68 text

    One more thing ... 68

    Slide 69

    Slide 69 text

    One more thing ... 69