代码之家  ›  专栏  ›  技术社区  ›  Michael K Madison

Ruby DSL for view为nil:NilClass生成错误“未定义的方法`safe_append='

  •  0
  • Michael K Madison  · 技术社区  · 7 年前

    我正在编写一个helper DSL,以便更容易在视图中创建一个漂亮的菜单ui。视图的erb正在产生错误 undefined method 'safe_append=' for nil:NilClass 当我在多个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 -%>
    

    但这是可行的:

              <%= @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 回复  |  直到 7 年前
        1
  •  6
  •   khaled_gomaa    7 年前

    我试着找一个例子来说明你想做什么,发现最接近它的是 form_for .

    然后我想知道为什么你的方式行不通。

    在跟踪代码的执行之后,似乎该块正在尝试呈现自己,假设它位于 ActionView::Context 例如,它将在哪里找到 Context#output_buffer 它找到的地方 nil 不能打电话 safe_append 在上面。

    现在如何解决这个问题。

    你必须确保你试图在视图中渲染的东西都有它自己渲染所需的所有上下文,这就是Rails以_的形式所做的

            <%= @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解析的相同行为。

        2
  •  1
  •   Danilo Cabello    7 年前

    这座大厦被授予了 start 当你收到“erb模板”和“方法调用列表”时,函数是不同的,在有效的情况下(方法调用),这就是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 。我没有为您提供正确的实施答案,但我建议您看看其他人的做法,例如,我知道雇员再培训局允许:

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

    希望这能帮助您最终实现DSL。

        3
  •  1
  •   ErvalhouS    7 年前

    当你用 <%= %> 这意味着它将打印一些东西,这些东西的输出与我们的工作类似 <% puts 'something' %> .自从你 start 方法需要一个块和 <%=%> 街区是 nil ,例外 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 %>
    

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