Search code examples
scalaplayframeworkplayframework-2.0scalaquery

ScalaQuery and Play framework: Best practice for handling unassigned (AutoInc) primary keys


I am using Play 2.0.2 with ScalaQuery 0.9.5.

I have the following simple model code:

case class Task(id: Long, name: String)

object Task extends Table[(Long, String)]("task") {

lazy val database = Database.forDataSource(DB.getDataSource())

def id = column[Long]("id", O PrimaryKey, O AutoInc)
def name = column[String]("name", O NotNull)

def create(task: Task) = database.withSession {
  implicit db: Session => {
    Task.name insert(task.name)
  }
}

And the following code for handling Form submission:

val taskForm: Form[Task] = Form(
  mapping(
    "name" -> nonEmptyText
  ) {
    (name) => Task(-1L, name)
  } {
    task => Some(task.name)
  }
)

def newTask = Action {
  implicit request =>
    taskForm.bindFromRequest.fold(
      errors => BadRequest(views.html.index(Task.all, errors)),
      task => {
        Task.create(task)
        Redirect(routes.Application.tasks())
      }
    )
}

A few questions:

1) Is there a better method for handling transient primary key values than passing in a constant? Something similar to Anorm's NotAssigned?

2) Would it be better to add "id" -> ignored(-1L) to the Form mapping and use the Task's extractor functions?

3) Should the case class rather be defined without an id field?


Solution

  • If a Task has an id you should not use this class to represent a value which has no id (yet). Anyway, that’s usual that a created object contains different data than what was filled in a form (e.g. creation date, etc.) so it usually does not make sense to use the same class to represent values needed to create an object and the created object itself.

    So you can either define another case class, e.g. TaskForm (which may eventually inherit from a common base of Task), or, in your case, just use a form yielding a String value:

    // Task
    case class Task(id: Long, name: String)
    
    object Task extends Table[(Long, String)]("task") {
      lazy val database = Database.forDataSource(DB.getDataSource())
    
      def id = column[Long]("id", O PrimaryKey, O AutoInc)
      def name = column[String]("name", O NotNull)
    
      def create(name: String) = database.withSession { implicit db: Session =>
        Task.name insert(name)
      }
    }
    
    
    // Application
    object Application extends Controller {
      val taskForm = Form("name" -> nonEmptyText)
      def newTask = Action { implicit request =>
        taskForm.bindFromRequest.fold(
          errors => BadRequest(views.html.index(Task.all, errors)),
          name => {
            Task.create(name)
            Redirect(routes.Application.tasks())
          }
        )
      }
    }