Challenges with RJS and Redirection

by Sean Cribbs

There are always serious conceptual challenges with building a large system, especially when you want to keep the responsibilities of each component clear and with as few side-effects as possible.

There’s one controller action in the not-so-stealth-anymore project (GiftLasso) I’m working on that begins multiple workflows. Naturally, we’ve been using Bruce Williams’ wonderful in_context plugin for most of these issues. The main point of this action is to create (or find) a model — I’ll call it model A — that needs to be present for the subsequent action. Once this first model is created, we redirect to the new action of the next controller so that the user can input some information and then create the associated model (Model B). Our application is CRUD-focused but doesn’t use REST as of yet. In the future, I’d prefer to create model A as we’re creating model B, but the separation works well for now and is conceptually cleaner. That pattern worked very nicely and cleanly until we wanted to add a little Ajax in the mix. The idea was to add pop-over boxes so that the user could stay in the same page, browsing through lists of Model A, and create instances of Model B as needed. However, since the lists of Model A are loaded through an external web service, and not necessarily created until someone tries to create a Model B, we need to go through Controller A’s create method as previously described.

“Ok,” I thought, “we’ll just let the Ajax request follow the redirection.” Unfortunately, when redirected, Prototype (or Firefox, I’m not sure which) doesn’t maintain those special headers that indicate the request is from Ajax. So the response I got back was the default HTML, complete with layout! So much for intuitive!

Then I thought, “Maybe the page.redirect_to method is the right way.” No luck, that actually redirects the whole browser page since it sends a window.location.href = "your url", which is exactly what we don’t want.

In the end the solution I came up with was to create a new Ajax request, like so:

  page << "new Ajax.Request('#{my_url}', {evalScripts: true, asynchronous: true});"

This creates quasi-redirection behavior I was looking for. Too bad it’s not the default!

There were a couple other things that I learned along the way. The create action for Controller A previously mentioned used to look like this (after its first refactoring):

  def create
    in_context do |context|
      context.context_one { create_context_one and return }
      context.context_two { create_context_two and return }
      # u.s.w.

However, I discovered that most of the delegated actions were really just loading Model A from the session or parameters, and then redirecting based on whether it was found and saved or not. Obviously this pattern wouldn’t work for RJS/Ajax.

So I abstracted out the generated flash messages and redirection URLs into hashes like so:

  @urls = { :one => proc {|a| one_url(:a_id => a) },
            :two => proc {|a| two_url(:a_id => a) } }
  @messages = { :one => "One failed.",
                :two => "Two failed." }

Blocks/procs/lambdas are wonderful for abstraction and refactoring. Make use of them! With the above, I could easily handle both the plain and the Ajax-based “redirection”.

  @context = params[:_context].to_sym
    my_url = @urls[@context].call(@a)
    respond_to do |format|
      format.html { redirect_to my_url }
      format.js { render :update do |page|
                    page << "new Ajax.Request('#{my_url}', {evalScripts: true, asynchronous: true});"
                  end }
    flash[:error] = @messages[@context]
    # u.s.w.

Of course, all of this is a distillation of what I did for the actual thing.

If you’re curious about what GiftLasso is, fear not, we’ll be releasing very soon!

p.s. u.s.w. is German, und so weiter, meaning “and so forth”.

© 2006-present Sean CribbsGithub PagesTufte CSS