Monday, December 27, 2010

I have settled on a new platform for webapps

Here it is: Play! Framework, Squeryl, and Scalate.

Getting them to work well together was a bit of a challenge. Play! has a collection of modules in different states of development. I got the best results using the most recent releases at the moment: play 1.1, play-scala 0.8, and squeryl 0.9.4RC3. The most recent scalate module 0.7.2 was too old, and I had to use the development version from github and compile it myself.

Here is one issue I run into:

Given an example controller and action:
object Manage extends ScalateController {
  def index = {
    val someVar = getSomeObjectFromSomewhere()
    Template(someVar) 
  }
}

The syntax hints that Template is an object that gets returned by the action. This is actually not correct. Play takes a bit of a strange approach when handling results in the controller. Result objects inherit from RuntimeException and are "thrown" from a controller's action and they are caught by framework. I suppose it makes it easy to break out of the flow in order to redirect, or something like that. So Template() is actually a method that "throws" a Result object.

This made it difficult at first to work with squeryl, which expects all database access to wrapper on a transaction{} block. For a whole day I kept trying to find the right way to do that trying different versions of the modules and different rendering methods. This was one of my attempts:

object Manage extends ScalateController {
  def index = transaction {
    // some squeryl database stuff 
    Template() 
  }
}

Of course it didn't work since the thrown results breaks the transaction. Then I created a base class that would catch and re-throw the result, something like this:

class MyBaseController extends ScalateController {
  def Template (_args:Any*) {
    var e:Result = null
    transaction { 
      try {
        super.Template(_args:_*)
      } catch {
        case r:Result => e = r
      }
    }
    throw e
  }
}

But that broke the "magic" parsing of local variables to send to the templates (maybe play does a little too much magic sometimes).

Finally I realized squeryl has a way to bind a session to the thread without using the transaction method. So, I was able to use that method and an @Before annotation to get the code to work:

class Manage extends ScalateController {
  @Before 
  def initSquerylSession = 
    SessionFactory.newSession.bindToCurrentThread

  def index = { 
    val projects = from(Repo.projects)(p => 
      where(p.public === 1) 
      select(p)
    ).toList

    Template(projects)
  }
}

Hopefully this post will save someone some time.

3 comments:

  1. Hi Alvaro, what would you think of defining a function such as this one :


    def playTransaction (u: Unit) {
    var e:Result = null
    transaction {
    try {
    u
    } catch {
    case r:Result => e = r
    }
    }
    throw e
    }

    Then whereever you would do

    transaction {
    ...
    }

    You would then do this :

    object Manage extends ScalateController {
    def index = playTransaction {
    // some squeryl database stuff
    }
    }

    the idea is to create a playTransaction that
    neutralizes the magic that play does via it's
    Result exception

    what do you think ?

    ReplyDelete
  2. Hi Maxime, thanks for dropping by. That approach is very similar to my second try, but I guess I was extending Template and maybe that's what broke the magic-parsing-of-variable-names.

    I'll try your approach when I get a chance.

    ReplyDelete
  3. Very cool, it worked for me. Thanks a lot for posting this! :)

    ReplyDelete