views:

377

answers:

1

This is lengthy, yes, I apologize. But I want to be clear because this issue is so odd.

I have a terms of service modal that comes up whenever a user has not accepted our terms of service (TOS). For the purposes here, javascript is turned off and what is rendered is strictly html/css. So the page, if thought of in 3 layers, is: bottom layer = page they came to see (i.e. an event page), middle layer: a semi-opaque overlay, top (primary/visible) layer: a modal with the terms of service agreement form in it. This form is simply an accept checkbox and submit button.

I noticed that the TOS was not working properly, but at seemingly random times. Then I noticed that it was broken but only on my event page (/event/foo) even though the same partial is responsible for showing the terms of service agreement no matter where they show up on the site. So for any other page, like /group/bar, the same TOS modal would show up and would work fine.

I then realized that the problem was that the form tag was missing from my HTML! Just gone.

So, taking a step back, the (HAML) code in question is simply:

%div#accept_tosC
  %b Before form_for
  - form_for @current_user do |form|
    %b After form_for
    %div#tosC= render :partial => 'general/terms'

    %div.left
      = render :partial => 'shared/user/tos_form_element'
    %div.right
      = image_submit_tag "/images/buttons/submit_100x20.png", :id => 'submit', :name => 'submit'

For our /events/foo page, the generated HTML look like this:

<div id="accept_tosC">
   <b>before form_for</b>
  <div style="margin: 0pt; padding: 0pt;"><input type="hidden" value="put" name="_method"><input type="hidden" value="44c2bf7a64fc59baa3fc7129167f0e2c3e96abb6" name="authenticity_token"></div>
      <b>after form_for</b>

The obvious question here is if 'Before form_for' and 'After form_for' make it into the document, why doens't the form tag form_for creates?

For a different page, say /groups/foo, we get exactly what we would expect:

<div id="accept_tosC">
  <b>before form_for</b>
  <form method="post" id="edit_user_595" class="edit_user" action="/users/595"><div style="margin: 0pt; padding: 0pt;"><input type="hidden" value="put" name="_method"><input type="hidden" value="44c2bf7a64fc59baa3fc7129167f0e2c3e96abb6" name="authenticity_token"></div>
    <b>after form_for</b>

I tracked it down to a single partial well inside the code for the "bottom" layer (the page they requested, not the TOS overlay). This partial in question may or may not be viewable to any given individual so we have to check if the user can view this page. The result of that is held in the variable can_view:

:ruby
  #some processing to set page info and can_view
  return unless can_view

%div#statsC
  ...and so on...

This is what my code looked like and the form tag did not render. By making the following change, the form element tag showed up as expected for all pages:

:ruby
  #some processing to set page info and can_view

- if can_view
  %div#statsC
    ...and so on...

So here is the question: Why would returning from a partial prevent a form element tag from becoming part of the document?

A: 

The short answer is that templates work in mysterious ways, and return is just generally not safe to use within them.

The long answer is that, behind the scenes, templates are compiled into Ruby methods that build up a string of HTML code and return that code. I'm guessing that by returning without returning the proper code, you're making something go haywire and discard the string that the form_for is concatenating to.

nex3
Thanks for the reply.Is this a known issue? Surely if returning from a partial blows up stuff from time to time the ruby folks know about it.
Jason
There's not a lot that can be done about it, I'm afraid. The way templates are compiled is a pretty hard requirement to get good performance, and it's not feasible to go in and rewrite all the Ruby return statements to return the proper thing.
nex3