Question

import com.escalatesoft.subcut.inject._
import com.mongodb.casbah.Imports._
import com.novus.salat._
import com.novus.salat.global._
import com.novus.salat.dao._

case class User(_id: ObjectId = new ObjectId, email: String, password: String)

class UserDAO(coll: MongoCollection = DatabaseClient.getCollection("users")) extends SalatDAO[User, ObjectId](
  collection = coll
)

class UserRepository(implicit val bindingModule: BindingModule) extends Injectable {
  val userDAO = injectOptional [UserDAO] getOrElse {new UserDAO}

  def createUser (email: String, password: String):Option[ObjectId] = {
    val newUser = User(email = email, password = password)
    val createdUser = userDAO.insert(newUser)
    createdUser
  }
}

Basically When inserting a new user it returns the Some("ObjectId of new user") which is exactly what I expect it to do. However when I put an Index on the email then I get a duplicate key error. What I would like is instead of getting the duplicate key error, getting the None option just like I do when I read from the collection and there is no matching document.

How can I get a None option when MongoDB returns the duplicate key error?

Or how should I be handling this error that I get back?

Was it helpful?

Solution 2

I'll give you two answers to this problem. The first one requires a slight change your approach. If the Salat DAO functionality is potentially throwing exceptions on insert, you might want to consider changing the createUser function to return a Try[Option[ObjectId]] instead and rework it like so:

def createUser (email: String, password: String):Try[Option[ObjectId]] = {
  val newUser = User(email = email, password = password)
  Try(userDAO.insert(newUser))  
}

Now the caller knows that the result will be one of three things: a Success(Some(objectId)), a Success(None) (not sure when this will happen but since it's an Option, you have to be able to handle it) or a Failure wrapping some exception. This way you could even pattern match on the exception in the Failure to make sure it's the one thrown on duplicate key and act accordingly as opposed to just swallowing any exception and assuming it must have been due to duplicate key.

Now if you really want a None for any failure, you could just redefine createUser like this:

def createUser (email: String, password: String):Option[ObjectId] = {
  val newUser = User(email = email, password = password)
  Try(userDAO.insert(newUser)).toOption.flatten 
} 

This will swallow any exception from insert and return a None.

OTHER TIPS

If you require the email to be unique, then your insert method should either catch and handle just the DuplicateKeyError (not every conceivable error - what if the write failed entirely? Wouldn't you want to know that?) or avoid this error entirely by checking to see if the unique key exists first.

I think a better approach is not to silence the error but rather to first search the collection using your unique key, "email", and if you find something, either update the existing user or just ignore the duplicate user - whatever your use case is.

Second, if you are using this for unit testing, then your unit tests should set up and tear down the test collection in such a way that each of your test cases runs with the external resource (the MongoDB collection) in a known state.

Here's an example of how Salat does this using specs2: https://github.com/novus/salat/blob/master/salat-core/src/test/scala/com/novus/salat/test/dao/SalatDAOSpec.scala

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top