Ruby DSL用于多个标签或多行

问题描述 投票:10回答:3

我正在编写一个辅助DSL,以便更容易在视图中创建一个漂亮的菜单。当我在多个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 

我错过了什么?

ruby-on-rails view block erb dsl
3个回答
6
投票

我试图找到你想要做的一个例子,并发现最接近它的是form_for

然后我试着找出为什么你的方式不起作用。

在跟踪代码的执行之后,似乎该块正在尝试渲染自己,假设它在ActionView::Context实例中,它将找到Context#output_buffer,在那里找到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
投票

当你收到“erb模板”vs“方法调用列表”时,给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的最终实施。


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

此外,删除标记中的减号,因为它可以避免表达式后的换行符。

© www.soinside.com 2019 - 2024. All rights reserved.