我正在编写一个辅助DSL,以便更轻松地在视图中创建漂亮的菜单ui。当我在多个erb标签之间断开该块时,视图的erb会产生错误undefined method 'safe_append=' for nil:NilClass
,但如果将其粘贴在一个标签中,它的效果很好。我想了解原因-它应该可以在多个标签中使用,并且更加自然。
这不起作用:
<%= @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 -%>
但这可行:
<%= @menu.start do
menu_item "#",
title: "Dashboard",
details: "12 New Updates",
icon: "fe:home",
first: true,
highlight: true
menu_item organizations_path,
title: "Organization",
details: "33k Updates",
icon: "fa:university"
end -%>
上述用于菜单的start
方法看起来像这样
def start(&block)
if block_given?
self.instance_eval(&block)
else
raise "menu expected a block!"
end
rescue => e
@logger.ap e.message, :error
@logger.ap e.backtrace, :error
ensure
if @menu_items.size > 0
return content_tag(:div, content_tag(:ul, self.display, class: "menu-items"), class:"sidebar-menu")
else
return "There is nothing to render here. Place an item in the menu"
end
end
我想念什么?
答案 0 :(得分:6)
我试图找到您要尝试做的事的一个例子,发现最接近的事是form_for
。
然后我试图找出为什么您的方式行不通。
在跟踪代码执行之后,似乎该块正在尝试呈现自身,前提是该块位于要在其中找到ActionView::Context
的{{3}}实例中的nil
实例中并且无法在其上调用safe_append
。
现在如何解决这个问题。
您必须确保要在视图中呈现的所有内容都具有呈现自身所需的所有上下文,这就是Rails在form_for中所做的
<%= @menu.start do |m| -%>
<% m.menu_item some_path_in_routesrb,
title: "Dashboard",
details: "12 New Updates",
icon: "feather:home",
highlight: true
%>
<% m.menu_item next_path,
title: "Magical stuff",
details: "unicorn registry",
icon: "fontawesome:rainbow",
highlight: true
%>
<% end -%>
并将其放在菜单类中
def start(&block)
if block_given?
yield self
else
raise "menu expected a block!"
end
rescue => e
@logger.ap e.message, :error
@logger.ap e.backtrace, :error
ensure
if @menu_items.size > 0
return content_tag(:div, content_tag(:ul, self.display, class: "menu-items"), class:"sidebar-menu")
else
return "There is nothing to render here. Place an item in the menu"
end
end
现在可以实现拥有eval_instance
的想法,但实际上并不是那么干净的恕我直言,因为这意味着您将尝试模仿ERB解析的相同行为。
答案 1 :(得分:1)
当您收到“ erb模板”与“方法调用列表”时,分配给start
函数的块是不同的,在可行的情况下(方法调用),这就是Ruby解释器:
@menu.menu_item("#",
title: "Dashboard",
details: "12 New Updates",
icon: "fe:home",
first: true,
highlight: true)
@menu.menu_item(organizations_path,
title: "Organization",
details: "33k Updates",
icon: "fa:university")
哪个是有效的Ruby。
在另一种情况下,您必须在尝试调用instance_eval
之前解析该模板字符串。我没有适合您的正确答案,但我建议您看看其他人的做法,例如,我知道ERB允许:
<% if @cost < 10 %>
<b>Only <%= @cost %>!!!</b>
<% else %>
Call for a price, today!
<% end %>
所以我看一下source code。
我知道允许这种构造形式的另一个库是liquid by shopify:
<ul id="products">
{% for product in products %}
<li>
<h2>{{ product.name }}</h2>
Only {{ product.price | price }}
{{ product.description | prettyprint | paragraph }}
</li>
{% endfor %}
</ul>
我还将通过查看source code来了解在这种情况下for循环是如何实现的。
希望可以帮助您最终实现DSL。
答案 2 :(得分:1)
使用<%= %>
构建块时,这意味着它将打印某些内容,其输出与进行<% puts 'something' %>
类似。由于您的start
方法需要一个块,而<%= %>
块的返回值为nil
,因此异常undefined method 'safe_append=' for nil:NilClass
会提示您要做什么。>
更改块以仅执行代码,以便将返回值传递到start
方法块中,如下所示:
<%= @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 %>
此外,请删除标签中的减号,因为这样可以避免在表达式后换行。