Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby DSL used across multiple <% %> tags or multiple lines

I am writing a helper DSL to make it easier to craft a nice menu ui within a view. The view's erb is producing an error undefined method 'safe_append=' for nil:NilClass when I break the block across multiple erb tags but it works fine if I stick it in one tag. I want to understand why -- it should work across multiple tags and is a lot more natural.

This doesn't work:

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

But this works:

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

The aforementioned start method for a menu looks like this

    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 

What am I missing?

like image 815
Michael K Madison Avatar asked Apr 19 '19 12:04

Michael K Madison


1 Answers

I tried to find an example of what you are trying to do and found that the closest thing to it is the form_for.

Then I tried to find why would your way not work.

After tracing the execution of code, it seems that the block is trying to render itself assuming it is inside the ActionView::Context instance where it is going to find Context#output_buffer where it finds nil and can't call safe_append on it.

Now how to solve this problem.

You have to make sure that whatever you are trying to render in the view has all the context it needs to render itself which is what Rails does in the 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 -%>

And have this in the menu class

 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 

Now the idea of having eval_instance can be done but won't be actually that clean IMHO as it means that you will try to mimic the same behaviour of ERB parsing.

like image 53
khaled_gomaa Avatar answered Oct 12 '22 00:10

khaled_gomaa