I had to solve a hairy problem recently. For a select number of pages on a site, I needed to pop up a secondary window along with the current action. This window contained a request for filling out a survey (held at a third party site). The survey URL was variable depending on the page I arrived at.

In the database, a survey had two basic parts: request_uri (e.g. “/home/about”) and url (e.g. “http://www.madeupsurvey.com/survey_1”). For every request, I needed to detect if the current URI had a survey entry, and if so I needed to popup the request page. Oh, and once I bothered the user for a particular survey, I needed to stop bothering her going forward.

Dip your toe in and test the water

In application.rb I added a before_filter to determine if a survey was in the cards for a particular request:

before_filter :detect_survey
def detect_survey
  survey = Survey.find_by_request_uri_part(request.env['REQUEST_URI'])

  if survey && !cookies[:surveys_taken].to_s.split(',').include?(survey.id.to_s)
    @survey = survey
  end
  
  if @survey
    cookies[:surveys_taken] = { :value => [cookies[:surveys_taken], @survey.id.to_s].compact.join(','),
                                :expires => Time.now + 10.years }
  end
end

A couple lines in the above code could use some further discussion. First:

if survey && !cookies[:surveys_taken].to_s.split(',').include?(survey.id.to_s)

I wanted to deal with an array, but cookies store strings. As you can see further down in the code, I’m storing the survey ids separated by commas. So getting an array is as easy as split(','). The to_s call immediately after grabbing the cookie protects from an error in the nil case - when no surveys_taken cookie is found.

cookies[:surveys_taken] = { :value => [cookies[:surveys_taken], @survey.id.to_s].compact.join(','), 
                            :expires => Time.now + 10.years }

This is the line where the cookie is stored. Again, I wanted to work with an array so I could use a snazzy join(',') call. To protect against a missing cookie (nil), I compact the array before joining it.

The end result is twofold. @survey holds the survey if one is found and it has not yet been presented to the user. Also, the surveys_taken cookie is updated with the latest accumulation of information.

Catch all your URLs

In my Survey model, I added one finder, which is used by the above application.rb code:

class << self
  # Find first survey with url_part.  Search for both url styles, 
  # ending with and without '/'.
  def find_by_request_uri_part(uri_part)
    last_slash_regex     = /\/$/
    alternative_uri_part = uri_part.match(last_slash_regex) ? 
                             uri_part.sub(last_slash_regex, '') :
                             uri_part + '/'
    find(:first, 
         :conditions => { :request_uri => [uri_part, alternative_uri_part] })
  end
end

Basically, if the request URI is /news, the method will look for surveys destined for /news and /news/, and visa-versa.

There ain’t no gettin’ around using JavaScript to open a second window, initiating a second request. I put this logic in a partial and included it at the very bottom of all appropriate layouts. Here’s what the partial looks like:

<% popup_survey_if_necessary do |survey_id| -%>
  <script type="text/javascript">
    window.open('<%= "/surveys/#{survey_id}" %>', '_survey', 'height=400,width=300,status=no,menubar=no,location=no,toolbar=no');
  </script>
<% end -%>

As you can see, I’m using a helper method to make the code nice and readable. This is in my application_helper.rb:

def popup_survey_if_necessary(&block)
  if @survey
    block.call @survey.id
  end
end

Looking at it now, that helper may be overkill. For some reason it seems more self-documenting, though. In any case, once JavaScript kicks open the new window, Rails takes over again and you’re on your merry way.

Nov 12, 2007 · Subscribe · Archive · Projects · Twitter · GitHub · Flickr