I have an idea how my data access layer with Scala Slick should look like, but I'm not sure if it's really possible.
Let's assume I have a User table which has the usual fields like id, email, password, etc.
object Users extends Table[(String, String, Option[String], Boolean)]("User") {
def id = column[String]("id", O.PrimaryKey)
def email = column[String]("email")
def password = column[String]("password")
def active = column[Boolean]("active")
def * = id ~ email ~ password.? ~ active
}
And I wish to query them in different ways, currently the ugly way is to have a new database session, do the for comprehension and then do different if statements to achieve what I want.
e.g.
def getUser(email: String, password: String): Option[User] = {
database withSession { implicit session: Session =>
val queryUser = (for {
user <- Users
if user.email === email &&
user.password === password &&
user.active === true
} //yield and map to user class, etc...
}
def getUser(identifier: String): Option[User] = {
database withSession { implicit session: Session =>
val queryUser = (for {
user <- Users
if user.id === identifier &&
user.active === true
} //yield and map to user class, etc...
}
What I would prefer is to have a private method for the query and then public methods which define queries along the lines of
type UserQuery = User => Boolean
private def getUserByQuery(whereQuery: UserQuery): Option[User] = {
database withSession { implicit session: Session =>
val queryUser = (for {
user <- Users
somehow run whereQuery here to filter
} // yield and boring stuff
}
def getUserByEmailAndPassword(email, pass){ ... define by query and call getUserByQuery ...}
getUserById(id){….}
getUserByFoo{….}
That way, the query logic is encapsulated in the relevant public functions and the actual querying and mapping to the user object is in a reusable function that other people dont need to be concerned with.
The problem I have is trying to refactor the "where" bit's into functions that I can pass around. Trying to do things like select them in intellij and using the refactoring results in some pretty crazy typing going on.
Does anyone have any examples they could show of doing close to what I am trying to achieve?
1) wrapping queries in a def means the query statement is re-generated on every single request, and, since query params are not bound, no prepared statement is passed to the underlying DBMS.
2) you're not taking advantage of composition
Instead, if you define parameterized query vals that def query wrappers call, you can get the best of both worlds.
val uBase = for{
u <- Users
ur <- UserRoles if u.id is ur.UserID
} yield (u,ur)
// composition: generates prepared statement one time, on startup
val byRole = for{ roleGroup <- Parameters[String]
(u,ur) <- uBase
r <- Roles if(r.roleGroup is roleGroup) && (r.id is ur.roleID)
} yield u
def findByRole(roleGroup: RoleGroup): List[User] = {
db withSession { implicit ss:SS=>
byRole(roleGroup.toString).list
}
}
If you need one-off finders for a single property, use:
val byBar = Foo.createFinderBy(_.bar)
val byBaz = Foo.createFinderBy(_.baz)
Can't remember where, maybe on SO, or Slick user group, but I did see a very creative solution that allowed for multiple bound params, basically a createFinderBy on steroids. Not so useful to me though, as the solution was limited to a single mapper/table object.
At any rate composing for comprehensions seems to do what you're trying to do.
I have recently done something similar, one way to do this could be following, write a general select method which takes a predicate
def select(where: Users.type => Column[Boolean]): Option[User] = {
database withSession { implicit session: Session =>
val queryUser = (for {
user <- Users where(user)
} //yield and map to user class, etc...
}
and then write the method which passes the actual predicate as a higher order function
def getUserByEmail(email:String):Option[User]={
select((u: Users.type) => u.*._2 === email)
}
similarly
def getActiveUserByEmail(email:String):Option[User]={
select((u: Users.type) => u.*._2 === email && u.*._4 === true)
}
Related
I'm a bit new to Scala
I'm trying to convert the code block starting from if... which looks like Java to something more Scala like (I think a flatmap or case) but I was unable to create the correct result for the function.
Any ideas?
Thanks
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] = {
def parseResponse(body: JsValue): Option[User] = body.asOpt[User]
if(request.headers.keys.contains("user")) {
val jsonObject: JsValue = Json.parse(request.headers.get("user").get)
val userOptional: Option[User] = parseResponse(jsonObject)
Future.successful(userOptional)
} else {
Future.successful(None)
}
}
Future.successful(request.headers.get("user").flatMap { value =>
val jsonObject: JsValue = Json.parse(value)
val userOptional: Option[User] = parseResponse(jsonObject)
userOptional
})
The conversion from request to User involves three levels of optionality (or "missingness"):
the "user" header could be missing,
the header value could be invalid JSON,
the JSON could not have the right shape to be deserialized into a User.
This multi-level optionality can be elegantly handled with a for-compehension, which will result in None if something is missing at any level, and in Some(user) if everything is good:
def userFromRequest(request: AuthenticatedRequest[A]): Option[User] =
for {
userHeader <- request.headers.get("user") // "user" header may be missing
userJson <- Json.parseOpt(userHeader) // header value may be not valid json
user <- userJson.asOpt[User] // json may be not convertible to user
} yield user
Note that I have taken out the Future from the logic, since this conversion has nothing to do with asynchronous calls.
Then, you can implement the getSubject method by calling the function above:
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
Future.successful(userFromRequest(request))
I am trying to implement a simple aggregation root in slick.
But I don't really know what is the best way to do that.
Here is my domain objects:
case class Project(id: UUID,
name: String,
state: ProjectState,
description: String,
team: String,
tags: Set[String]
I would like to store the "tags" in a separate table and build up the "Project" objects from "projects_table" and "project_tags_table"
Here is my table definition:
class ProjectTable(tag: Tag) extends Table[ProjectTableRecord](tag, Some("octopus_service"), "projects") {
def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey)
def name: Rep[String] = column[String]("name")
def state: Rep[ProjectState] = column[ProjectState]("state")
def description: Rep[String] = column[String]("description")
def team: Rep[String] = column[String]("team")
override def * : ProvenShape[ProjectTableRecord] = (id, name, state, description, team, created, lastModified) <> (
(ProjectTableRecord.apply _).tupled, ProjectTableRecord.unapply
)
}
class ProjectTagTable(tag: Tag) extends Table[ProjectTag](tag, Some("octopus_service"), "project_tags") {
def projectID: Rep[UUID] = column[UUID]("project_id")
def name: Rep[String] = column[String]("name")
def project = foreignKey("PROJECT_FK", projectID, TableQuery[ProjectTable])(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
override def * : ProvenShape[ProjectTag] = (projectID, name) <> (
ProjectTag.tupled, ProjectTag.unapply
)
}
How can I generate "Project" objects from joining these 2 tables?
Thanks in advance :)
I think there is a misconception on the level of responsibility. Slick allows you to access relational database (to some extent the same way as SQL allows you to do it). It's basically a DAO layer.
Aggregate root is really a level above this (it's a domain thing, not db level thing - although they often are the same to large extent).
So basically you need to have a level above Slick tables that would allow you to perform different queries and aggregate the results into single being.
Before we start though - you should create and store somewhere your TableQuery objects, perhaps like this:
lazy val ProjectTable = TableQuery[ProjectTable]
lazy val ProjectTagTable = TableQuery[ProjectTagTable]
You could put them probably somewhere near you Table definitions.
So first as I mentioned your Aggregate Root being Project needs be pulled by something. Let's call it ProjectRepository.
Let's say it will have a method def load(id: UUID): Future[Project].
This method would perhaps look like this:
class ProjectRepository {
def load(id: UUID): Future[Project] = {
db.run(
for {
project <- ProjectTable.filter(_.id === id).result
tags <- ProjectTagTable.filter(_.projectId === id).result
} yield {
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = tags.map(_.name)
)
}
)
}
// another example - if you wanted to extract multiple projects
// (in reality you would probably apply some paging here)
def findAll(): Future[Seq[Project]] = {
db.run(
ProjectTable
.join(ProjectTag).on(_.id === _.projectId)
.result
.map { _.groupBy(_._1)
.map { case (project, grouped) =>
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = grouped.map(_._2.name)
)
}
}
)
}
}
Digression:
If you wanted to have paging in findAll method you would need to do something like this:
ProjectTable
.drop(pageNumber * pageSize)
.take(pageSize)
.join(ProjectTag).on(_.id === _.projectId)
.result
Above would produce sub-query but it is basically typical way how you do paging with multiple joined relations (without subquery you would page over whole result set which is most of the time not what you need!).
Coming back to main part:
Obviously it would be all easier if you defined you defined your Project as:
case class Project(project: ProjectRecord, tags: Seq[ProjectTag])
then your yield would be simply:
yield {
Project(project, tags)
}
but that's definitely a matter of taste (it make actually sense to make it as you did - to hide internal record layout).
Basically there are potentially multiple things that could be improved here. I am not really an expert on DDD but at least from Slick perspective the 1st change that should be done is to change the method:
def load(id: UUID): Future[Project]
to
def load(id: UUID): DBIO[Project]
and perform db.run(...) operation on some higher level. The reason for this is that in Slick as soon as you fire db.run (thus convert DBIO to Future) you loose ability to compose multiple operation within single transaction. Therefore a common pattern is to push DBIO pretty high in application layers, basically up to some business levels which defined transactional boundaries.
I am trying to build a simple RESTful service that performs CRUD operations on a database and returns JSON. I have a service adhering to an API like this
GET mydomain.com/predictions/some%20string
I use a DAO which contains the following method that I have created to retrieve the associated prediction:
def getPrediction(rawText: String): Prediction = {
val predictionAction = predictions.filter{_.rawText === rawText}.result
val header = predictionAction.head
val f = db.run(header)
f.onComplete{case pred => pred}
throw new Exception("Oops")
}
However, this can't be right, so I started reading about Option. I changed my code accordingly:
def getPrediction(rawText: String): Option[Prediction] = {
val predictionAction = predictions.filter{_.rawText === rawText}.result
val header = predictionAction.headOption
val f = db.run(header)
f.onSuccess{case pred => pred}
None
}
This still doesn't feel quite right. What is the best way to invoke these filters, return the results, and handle any uncertainty?
I think the best way to rewrite your code is like this:
def getPrediction(rawText: String): Future[Option[Prediction]] = {
db.run(users.filter(_.rawText === rawText).result.headOption)
}
In other words, return a Future instead of the plain result. This way, the database actions will execute asynchronously, which is the preferred way for both Play and Akka.
The client code will then work with the Future. Per instance, a Play action would be like:
def prediction = Action.async {
predictionDao.getPrediction("some string").map { pred =>
Ok(views.html.predictions.show(pred))
}.recover {
case ex =>
logger.error(ex)
BadRequest()
}
}
I have model repository class with byId and save metdhods
def byID(id:Long) = db.run{ query.filter(_.id === id).result }.map(_.headOption)
def save(model:User) = db.run{ query.filter(_.id===model.id).update(model) }
Now I want to use both these methods, first load user, then change something, and then save user, like this
userRepository.byID(5L).map{_.map{user =>
val newuser = user.copy(name = "John")
userRepository.save(newuser)
}}
How can I do it in one transaction?
I think that slick 3 doesn't support having a transaction that spans over different futures and when you call db.run you are passing in a DBIO[SomeType] in and getting a Future out. The good news is that you can instead compose your DBIOs structuring your API in a slightly different way:
def byID(id:Long) = query.filter(_.id === id).result }.map(_.headOption)
def save(model:User) = query.filter(_.id===model.id).update(model)
And then:
db.run(userRepository.byID(5L).flatMap { users =>
DBIO.sequence(users.map { user =>
userRepository.save(user.copy(name = "John"))
})
}.transactionally)
Selecting a single row by id should be a simple thing to do, yet I'm having a bit of trouble figuring out how to map this to my object.
I found this question which is looking for the same thing but the answer given does not work for me.
Currently I have this that is working, but it doesn't seem as elegant as it should be.
def getSingle(id: Long):Option[Category] = withSession{implicit session =>
(for{cat <- Category if cat.id === id} yield cat ).list.headOption
//remove the .list.headOption and the function will return a WrappingQuery
}
I feel getting a list then taking headOption is just bulky and unnecessary. I must be missing something.
If it helps, here is more of my Category code
case class Category(
id: Long = 0L,
name: String
)
object Category extends Table[Category]("categories"){
def name = column[String]("name", O.NotNull)
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def * = id ~ name <> (Category.apply _, Category.unapply _)
...
}
Is there an easier way to just get an Option[T] from an ID using Slick?
Solution There was a driver issue. I couldn't use .firstOption but upgraded to mysql jdbc 5.1.25 and all is well!
You can do this:
def getSingle(id: Long):Option[Category] = withSession{implicit session =>
Query(Category).where(_.id === id).firstOption
}
If you use this query quite often then you should consider QueryTemplate:
val byId = t.createFinderBy( t => t.id )
This will create a precompiled prepared statement that you can use from your method
def getSingle(id: Long):Option[Category] = byId(id).firstOption
Firstly, you may try is to use desugared version of the same code:
Category.filter{ _.id === id }.list.headOption
It looks much cleaner.
Also you may use firstOption method:
Category.filter{ _.id === id }.firstOption
I am using slick 1.0.1 with Play 2.2.1 and the following works for me.
val byId = createFinderBy(_.id)
Then call it from a method.
def findById(id: Int): Option[Category] = DB.withSession { implicit session =>
Category.byId(id).firstOption
}
Please note, the DB.withSession is a method from the play framework.
If you are not using Play, the method would be something like below.
def findById(id: Int)(implicit session: Session): Option[Category] = {
Category.byId(id).firstOption
}