Help us understand the problem. What is going on with this article?

ERB利用時の _erbout NameError

More than 1 year has passed since last update.

ERBを使用してテンプレートを使おうとした際、
erbファイル側にメソッド定義を含む記述をした場合、
_erboutのNameErrorが発生。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require "erb"
params = {name: "John"}
trim_mode = "<>"

erb = ERB.new(DATA.read, nil, trim_mode)
puts erb.result_with_hash(**params).strip

__END__
<%# -*- coding: utf-8 -*- %>
<%# frozen_string_literal: true %>
<%  def hello(name) %>
 <%    "hello #{name}!" %>
<%  end %>
<%= "#{hello(name)}" %>

実行結果

$ ./erb_sample.rb 
Traceback (most recent call last):
        5: from ./erb_sample.rb:11:in `<main>'
        4: from ~/.rbenv/versions/2.5.1/lib/ruby/2.5.0/erb.rb:887:in `result_with_hash'
        3: from ~/.rbenv/versions/2.5.1/lib/ruby/2.5.0/erb.rb:876:in `result'
        2: from ~/.rbenv/versions/2.5.1/lib/ruby/2.5.0/erb.rb:876:in `eval'
        1: from (erb):6:in `<main>'
(erb):4:in `hello': undefined local variable or method `_erbout' for main:Object (NameError)

エラー発生ポイントは、erbファイル(に相当する__END__以降)にて
行頭に空白がある場所。

何が原因か、また、_erboutとは何かが不明であったため確認した。

結論から言うと、trim_modeの指定とeRubyタグの組み合わせ、動作の理解を間違っていたから。

ERBについては、以下のサイトを参考に。

標準添付ライブラリ紹介 【第 10 回】 ERB

確認した部分

ERBは、erbファイル(相当の文字列)を1行ずつ処理して、決まったルールで書き換える。

eRubyタグおよびeRubyタグで囲まれているものを除き、基本はスペースも改行も文字もすべて出力すべきものとして処理する。

出力というのが何かわからなかったのだが、ERB#resultやERB#result_with_hashで返される値(Stringのインスタンス)のこと。

出力は、resultやresult_with_hashメソッドの中で、_erboutという変数に追加で貯められていく。
これが_erboutの正体。

見やすく加工しているが、こんな感じで貯められていく。

_erbout = +''
_erbout.<< "\n".freeze
_erbout.<< "\n".freeze

resultやresult_with_hashの最後に、_erboutの値が返される = 出力。

上記で言う、基本はの部分に影響を及ぼすのが、
ERB.newの第3引数で指定するtrim_mode

erbファイル内でメソッド定義を使用とした時に、
次のような挙動となってエラーが出力されていた。

trim_modeは、"<>"。

<%  def hello(name) %>
 <%    "hello #{name}!" %>
<%  end %>
<%= "#{hello(name)}" %>

この内容をERBで処理すると、次のように変換(確認しやすいように、変換結果を一部整形している)される。

_erbout.<< "\n".freeze

def hello(name)
  _erbout.<< "  ".freeze  (1)
  "hello #{name}!"
  _erbout.<< "\n".freeze  (2)
end

_erbout.<< "\n".freeze
_erbout.<< "#{hello(name)}"
_erb_out

変換された結果全体を文字列として@srcというインスタンス変数に代入し、
その後、evalによって、@srcが評価される。

evalによる評価結果は、_erboutの中身が返ってくることになるので
出力というのは、_erboutの中身に入ってる文字列を出力する、という意味であると考えられる。

前述のサイト標準添付ライブラリ紹介 【第 10 回】 ERBより

‘<>’ は行頭が「<%」かつ行末が「%>」の行の改行を出力しません。 「<% end %>」とそれに対応する「<% if … %>」や「<% ….each do %>」などの改行を出力したくない時に使えます。 行頭と行末の両方を見て改行を出力するかどうかを決めているので、trim_mode が ‘-‘ の時の「<%- … -%>」と違って、行頭がインデントされていると行頭の空白も改行も出力されてしまいます。

ifdo ~ endの場合は、ブロックの外の変数_erboutが、ブロック内でも見えるので
エラーにはならないが、defなどスコープが切り替わるものの後だと
_erboutが見えない(未定義)。

上記で言うと、(1)と(2)のところで、_erboutを参照しているのでエラーとなる。

trim_modeを"-"にすると、

行末-%>のとき改行を出力しない。また、 行頭<%-のとき 行頭 の空白文字を削除する

挙動となるため(https://docs.ruby-lang.org/ja/latest/class/ERB.html)
次のように変更するとメソッド定義内に_erboutが存在しなくなるのでエラーは発生しない。

以下のようにするとエラーはでなくなるが。

<% def hello(name) -%>
 <%-   "hello #{name}!" -%>
<% end -%>
<%= "#{hello(name)}" %>

以下のケースでは(3)の部分で_erboutが登場するので
エラーが発生する。

<% def hello(name) -%>
                        …(3)
 <%-   "hello #{name}!" -%>
<% end -%>
<%= "#{hello(name)}" %>

もろもろ考慮して全体を考えるとこういう書き方が良さそう。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require "erb"

params = {name: "John"}

trim_mode = "-"

erb = ERB.new(DATA.read, nil, trim_mode)
puts erb.result_with_hash(**params).strip

__END__
<%# -*- coding: utf-8 -*- %>
<%# frozen_string_literal: true %>
<% def hello(name)

     "hello #{name}!"
   end -%>
<%= "#{hello(name)}" %>

実行結果

$ ./erb_sample.rb 
hello John!

その他

_erboutを考慮した同じ考え方で以下の挙動も理解可能。

if文を利用したときの挙動

<% val = "hoge" %>
<% if val == "hoge" %>
  hogehoge
<% else %>
  foobar
<% end %>

hogehogeはfoobarは、rubyのコードとして処理されない地の文であるはずなのに、
ifの条件分岐で選ばれる。

do endを利用したときの挙動

<% 3.times do %>
  hogehoge
<% end %>

rubyのコードではないはずの文字列"hogehoge¥n"が
3回繰り返された結果が返る。など。

確認時に利用したもの

ERB#src

なお、ERBのインスタンスに対して、ERB#srcメソッドを呼ぶと、
@srcの内容を確認できる。

https://docs.ruby-lang.org/ja/latest/method/ERB/i/src.html

GitHub上のerb.rb

https://github.com/ruby/ruby/blob/cd8b9033e625d84fc0cae4d8938c5afd0bfd48ce/lib/erb.rb#L876

pry-byebug

pry-byebugを利用したデバッグ。

# 事前に`pry-byebug`のgemをインストールしておく必要あり。
# デバッグしたい箇所に以下のコードを入れて実行する 
require "pry-byebug"; binding.pry
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした