KatrinaHoffert / EatSafe

An app for finding safe places to eat
Other
2 stars 2 forks source link

Create test hooks for database #39

Closed KatrinaHoffert closed 9 years ago

KatrinaHoffert commented 9 years ago

This can be done by creating a custom type that stores the name of the database to use (recommended name: ActiveDatabase). The Play Framework's connections can specify the DB name, which we would get from this custom type.

Then every function that works on the database takes in an ActiveDatabase implicitly. This is why it must be a custom type: implicit parameters look for an implicit variable of a given type and use the first found.

Create a "globals" page (suggested name: Globals.scala) that is a package object and have it contain an instance of ActiveDatabase with the non-testing database (declare it as implicit and lazy). This must only be done for controllers and will only work for directly accessing the model. So for integration tests, you have to use the normal database. No way around that.

In the non-testing pages, we include this (ie, with import Globals._). Testing pages will declare their own implicit ActiveDatabase (or make their own globals file).

magnusandy commented 9 years ago

I do not really understand where the implicit class will be added, I created a Implicit ActiveDatabase for the default database,

class ActiveDatabase(name : String) {
  val dbName = name
}

package object globals {
  implicit lazy val database = new ActiveDatabase("default")
}

But I don't really understand how the implicit parameters work

KatrinaHoffert commented 9 years ago

First of all, note that the class can be simplified to merely class ActiveDatabase(val name: String). That will do the same as you've written, but the parameter will be name.

Anyway, now when you import globals (should be named Globals for consistency), the implicit value is in scope of the entire file. So then our model functions would take in a curried, implicit parameter (implicit parameters have to be curried).

Sample syntax: def getLocation(locationId: Int)(implicit activeDb: ActiveDatabase): Try[Location]. But it would be used the exact same way (ie, we don't have to specify the implicit parameter -- it'll be passed automatically).

We'd then use the variant of play.api.db.DB.withConnection that takes in the database name as a parameter (see the docs).

EDIT: The above link isn't working because Github breaks it... Copy it from here: https://www.playframework.com/documentation/2.0/api/scala/play/api/db/DB$.html.

magnusandy commented 9 years ago

This is what I have been trying to do but have been running into problems. I changed the getLocation exactly as you just said, but then the calls in the controller need a implicit database, which I cannot seem to get from just importing the globals._

KatrinaHoffert commented 9 years ago

You imported that in the controller, right? If you're getting an error, what is it?

magnusandy commented 9 years ago

globals._ is imported in LocationController but there is an error

  def showLocation(locationId: Int) = Action {
    Location.getLocationById(locationId) match {
      case Success(location) =>
        Ok(views.html.locations.displayLocation(location))
      case Failure(ex) =>
        // TODO: Create a prettier error page
        InternalServerError("Encountered an error.\n\nDetails: " + ex.toString)
    }
  }

on line 2 the error is

could not find implicit value for parameter db: globals.ActiveDatabase not enough arguments for method getLocationById: (implicit db: globals.ActiveDatabase)scala.util.Try[models.Location]. Unspecified value parameter db.

KatrinaHoffert commented 9 years ago

What's the full signature of getLocationById? What does the globals file look like?

I quickly threw together some code on my end and there's no errors. I copied the package object you previously mentioned into a file named Globals.scala and imported it into the LocationController. The signature of getLocationById is def getLocationById(locationId: Int)(implicit active: ActiveDatabase): Try[Location]. And I defined ActiveDatabase as class ActiveDatabase(val name: String).

magnusandy commented 9 years ago

This is now finished Any methods that work with the database should have (implicit val activeDB: ActiveDatabase) as a parameter and use that ActiveDatabase to specify which database to connect to via

DB.getConnection(activeDB.name)  {  conn =>
  //use the connection in some way
}