Search code examples
scalaliftlifecycle

What is the lifetime of an object when using Lift?


I'm completely new to Lift. My goal is to make a small application which will let me enter data about scientific articles (studies) into a database.

I haven't gotten around to making a database yet, still playing with getting an entry form to work. So I decided that I will hold a few studies in memory, in a list of a companion object to the Study class. I also created an accumulator variable to dispense unique IDs, as long as I don't have a DBMS managing the IDs.

As a smoke test, I visited the page showing the list of studies (seeded with two studies in code), then visited the form page, entered a new study, and navigated again to the list of studies. I was surprised to see that my new study has the ID of 1 instead of 3, so at some point, my accumulator variable must have been reset. But the ListBuffer collecting studies was not reset, because it showed all three studies. Adding more studies results in the counter incrementing by 1 every time.

The literature I have found on Lift (the two free books on Liftweb, as well as Gilberto Garcia's Lift Application Development Cookbook) are incomplete and are more like a collection of mini-tutorials, they don't explain how Lift works.

So what is the actual lifecycle of my Study object, and why did one mutable variable get reset after re-opening the page but not another?

package org.rumtscho.litsuche
package study

import scala.collection.mutable.ListBuffer

class Study private[study](
    val id: Int,
    val handmadeAuthorRef: String,  
    val humanReadableDescription: String ) 
{
}

object Study {
  val seedStudies = List(
          new Study(dispenseNextFreeId, "Brooks1975", "Tells us that throwing more programmers at a project makes it run late, not early"), 
          new Study(dispenseNextFreeId, "Dijkstra1968", "Recognizes that we need structured code")
      )

   private var studiesList = seedStudies.to[ListBuffer]
   private var currentId = 0


   private def dispenseNextFreeId: Int = {
    currentId = currentId + 1
    return currentId
  }

   def allStudies = studiesList.toList

   def addStudy(reference: String, description: String): Unit = {
     studiesList += new Study(dispenseNextFreeId, reference, description)
  }
}

Here is the representation of the three studies:

The output of the above

update My understanding of what is happening (could be wrong, of course):

  1. I open the page showing the list of studies. This calls allStudies. studiesList is initialized to contain Brooks1975 and Dijkstra1968. During the construction of the studies, currentId is initialized to 0 and then incremented to 2.

  2. I open the entry form and add a third study. addStudy retrieves allStudies from memory, without initializing it again. It initializes currentId to 0. I end up with a third study with the ID 1.

  3. I display all studies, then return to the entry form page. This time, addStudy retrieves both allStudies and currentId from memory. I get a fourth study with the ID of 2.

The comments have pointed out that this is probably Scala-specific and not related to Lift. But still, I don't understand why currentId is initialized two times (in steps 1 and 2), and not either once (as soon as the object itself is created) or every time it is read. I would have expected the first behavior, but even reinitializing every time seems more logical than randomly reinitializing one time only.


Solution

  • Go into the scala REPL, enter paste mode (:paste) command, and put in the following:

    def increment {
      currentId = currentId + 1
    }
    
    increment
    increment
    
    var currentId = 0
    

    then try

    var currentId = 0
    
    def increment {
      currentId = currentId + 1
    }
    
    increment
    increment
    

    In the first example, currentId ends up with value 0. In the second, it ends up with value 2. Why does this happen? I'm not an expert on Scala declaration, but it seems that this is the same problem you are running in to.

    It seems that the solution is as @jcern suggests. In general, I'd say put all your declarations at the top of your classes or objects, and always declare before using a variable, and not the other way around.