跨多个<%%>标签或多行使用的Ruby DSL

时间:2019-04-19 12:11:06

标签: ruby-on-rails view block erb dsl

我正在编写一个辅助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 

我想念什么?

3 个答案:

答案 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 %>

此外,请删除标签中的减号,因为这样可以避免在表达式后换行。