patbenatar / rbexy

A Ruby template language and component framework inspired by JSX and React
MIT License
34 stars 5 forks source link

Debuggers in template code #66

Closed lunks closed 3 years ago

lunks commented 3 years ago

Given I'm doing something like:

<%= render "product", collection: @products %>

And I have binding.pry inside my rbx _product.rbx partial, I get in a weird place where I can't access product:

From: /Users/lunks/.asdf/installs/ruby/2.7.2/lib/ruby/gems/2.7.0/gems/rbexy-1.1.0/lib/rbexy/view_context_helper.rb:8 Rbexy::ViewContextHelper#rbexy_prep_output:
     7: def rbexy_prep_output(*value)
 =>  8:   return if value.length == 0
     9:   value = value.first
    10: 
    11:   value = rbexy_is_html_safe_array?(value) ? value.join.html_safe : value
    12:   [nil, false].include?(value) ? "" : value.to_s
    13: end
[1] pry(#<#<Class:0x00007ff7684c72c0>>)> product
NameError: undefined local variable or method `product' for #<#<Class:0x00007ff7684c72c0>:0x00007ff7684c5678>
patbenatar commented 3 years ago

@lunks

I looked into this a bit today. It appears the same issue is present with ERB templates:

<%= binding.pry %>

Leaves you here:

From: /usr/local/bundle/gems/actionview-6.0.3.3/lib/action_view/buffers.rb:28 ActionView::OutputBuffer#<<:

    27: def <<(value)
 => 28:   return self if value.nil?
    29:   super(value.to_s)
    30: end

However, ERB has the quiet syntax like <% binding.pry %> which avoids the problem because it doesn't get compiled to output_buffer << binding.pry but rather just binding.pry. Since we don't have a quiet syntax, we can't use that workaround in templates.

Adding a quiet syntax would be a good bit of work and starts to deviate from our origins in JSX, so I'd like to avoid it. But I do think getting a debugger in the template is a useful feature. I'm thinking it might be simplest to add special handling for expressions that just contain common debuggers (like {debugger} and {binding.pry}) and don't try to send those to the output buffer.

What do you think?

patbenatar commented 3 years ago

We already have the ability to control what template Ruby code is used while compiling each expression (https://github.com/patbenatar/rbexy/blob/master/lib/rbexy/nodes/expression_group.rb#L10), so I don't think it would be too tough to check the expression's content and use a raw template (%s) instead of an output template.,

lunks commented 3 years ago

I’m more than ok treating this as a special case. Agreed having a quiet syntax doesn’t make sense for rbexy, even if not having one tricks me from time to time.

On Mon, 18 Jan 2021 at 21:36 Nick Giancola notifications@github.com wrote:

We already have the ability to control what template Ruby code is used while compiling each expression ( https://github.com/patbenatar/rbexy/blob/master/lib/rbexy/nodes/expression_group.rb#L10), so I don't think it would be too tough to check the expression's content and use a raw template (%s) instead of an output template.,

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/patbenatar/rbexy/issues/66#issuecomment-762530085, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAWH3UBT2OO2RL7INYIX4LS2THY5ANCNFSM4VNV72TA .

patbenatar commented 3 years ago

@lunks hmm.. on further investigation, this doesn't seem to work even with a quiet erb tag:

<h1><% binding.pry %></h1>

Gives me this binding:

From: /rails/activesupport/lib/active_support/core_ext/string/output_safety.rb:169 ActiveSupport::SafeBuffer#safe_concat:

    168: def safe_concat(value)
 => 169:   raise SafeConcatError unless html_safe?
    170:   original_concat(value)
    171: end

Which is the same result as when I tried the special-casing of binding.pry in Rbexy compiled output to be "quiet".

It appears that ERB will only put you in the right spot if the binding is on its own line, like so:

<h1>
  <% binding.pry %>
</h1>

It turns out the secret is you need a newline in the compiled Ruby code after the binding.pry statement in order to have the binding not placed into the next method call. I was able to accomplish this by just always adding a \n to the compiled output of a debugger expression.

This works, but it means that we end up with double newlines in the compiled output because Rbexy also compiles the newline that it found in the rbx source as a text output.. not a big deal except that the line no mapping from src to compiled output ends up off by one for lines following the debugger line, so if exceptions occur on those lines the error line will be off by one...

I'm thinking this trade-off is worth it, because the use-case for a debugger suggests you may have already hit an exception and are trying to dig deeper into it. So the exception being off by one at this point is probably not that big of a deal. Thoughts?