跨多个ERB标签使用Ruby DSL报错:undefined method 'safe_append='
这个问题的根源其实在于ERB的输出机制和你menu_item方法的返回值处理上,我们一步步拆解来看:
为什么拆分ERB标签会报错?
当你把代码拆分成多个<%= ... %>标签时,每个标签都会尝试将内部代码的返回值输出到ERB的输出缓冲区。而你的menu_item方法应该只是负责将菜单项添加到@menu_items集合中,并没有返回一个可被ERB安全处理的字符串(大概率返回的是nil)。
ERB在处理输出时,会调用safe_append=方法来将内容追加到缓冲区,但如果要追加的值是nil,就会触发undefined method 'safe_append=' for nil:NilClass这个错误。
而在单个<%= ... %>标签的写法中,只有整个start方法的返回值(也就是最终渲染好的菜单HTML)会被ERB输出,block内部的menu_item调用的返回值会被忽略,自然不会触发这个问题。
解决办法
有两种简单的修复方式,推荐第一种:
1. 去掉menu_item前的等号
既然menu_item只是用来构建菜单数据,不需要输出任何内容,把<%= menu_item ... %>改成<% menu_item ... %>即可,这样ERB只会执行方法,不会尝试输出它的返回值:
<%= @menu.start do -%> <% menu_item some_path_in_routesrb, title: "Dashboard", details: "12 New Updates", icon: "feather:home", highlight: true %> <% menu_item next_path, title: "Magical stuff", details: "unicorn registry", icon: "fontawesome:rainbow", highlight: true %> <% end -%>
2. 修改menu_item返回安全空字符串
如果你坚持要用<%= %>(虽然没必要),可以让menu_item方法返回一个空的HTML安全字符串,这样ERB就有合法的内容可以追加:
def menu_item(path, options) # 原来的添加逻辑不变 @menu_items << { path: path, **options } # 返回空的安全字符串 ''.html_safe end
补充说明
你用instance_eval(&block)让block在菜单实例的上下文中执行,这部分是没问题的——问题核心完全是ERB的输出逻辑,和instance_eval的上下文无关。只要确保block内部的方法调用不会被ERB当作输出内容处理,就能避免这个错误。
内容的提问来源于stack exchange,提问作者Michael K Madison

