Ruby DSL используется для нескольких тегов <%%> или нескольких строк - PullRequest
10 голосов
/ 19 апреля 2019

Я пишу вспомогательный DSL, чтобы упростить создание приятного пользовательского интерфейса в представлении.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 

Чего мне не хватает?

Ответы [ 3 ]

6 голосов
/ 03 мая 2019

Я попытался найти пример того, что вы пытаетесь сделать, и обнаружил, что наиболее близким к нему является 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.

1 голос
/ 22 апреля 2019

Когда вы строите блоки с <%= %>, это означает, что он напечатает что-то, что будет похоже на <% 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 %>

Кроме того, удалите знак минус в своих тегах, поскольку это позволит избежать разрывов строк после выражения.

1 голос
/ 21 апреля 2019

Блок, переданный функции 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. У меня нет правильного ответа для реализации, но я бы посоветовал посмотреть, как это делают другие, например, я знаю, что ERB позволяет:

<% if @cost < 10 %>
  <b>Only <%= @cost %>!!!</b>
<% else %>
  Call for a price, today!
<% end %>

Так что я бы посмотрел на исходный код .

Другая известная мне библиотека, которая допускает такую ​​форму построения, - 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>

Я бы также посмотрел, как в этом случае реализован цикл for, взглянув на исходный код .

Надеюсь, это поможет вам окончательно реализовать DSL.

...