I am working in a project with scala play 2 framework where i am using slick as FRM and postgres database.
In my project customer is an entity. So i create a customer table and customer case class and object also. Another entity is account. The code is given bellow
case class Customer(id: Option[Int],
status: String,
balance: Double,
payable: Double,
created: Option[Instant],
updated: Option[Instant]) extends GenericEntity {
def this(status: String,
balance: Double,
payable: Double) = this(None, status, balance, payable, None, None)
}
class CustomerTable(tag: Tag) extends GenericTable[Customer](tag, "customer"){
override def id = column[Option[Int]]("id")
def status = column[String]("status")
def balance = column[Double]("balance")
def payable = column[Double]("payable")
def account = foreignKey("fk_customer_account", id, Accounts.table)(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * = (id, status, balance, payable, created, updated) <> ((Customer.apply _).tupled, Customer.unapply)
}
object Customers extends GenericService[Customer, CustomerTable] {
override val table = TableQuery[CustomerTable]
val accountTable = TableQuery[AccountTable]
override def copyEntityFields(entity: Customer, id: Option[Int],
created: Option[Instant], updated: Option[Instant]): Customer = {
entity.copy(id = id, created = created, updated = updated)
}
}
Now the problem is how can i create an account object inside the customer object? Or how can i get the account of a customer?
Slick is not ORM its just a functional relational mapper. Nested objects cannot be directly used in slick like Hibernate or any other ORMs Instead
Slick
//Usual ORM mapping works with nested objects
case class Person(name: String, age: Int, address: Address) //nested object
//With Slick
case class Address(addressStr: String, id: Long) //id primary key
case class Person(name: String, age: Int, addressId: Long) //addressId is the id of the address object and its going to the foreign key
Account object in Customer
case class Account(id: Long, ....) // .... means other fields.
case class Customer(accountId: Long, ....)
Place accountId inside the Customer object and make it a foreign key
For more info refer to Slick example provided as part of documentation
Related
I have written a simple Play! 2 REST with Slick application. I have following domain model:
case class Company(id: Option[Long], name: String)
case class Department(id: Option[Long], name: String, companyId: Long)
class Companies(tag: Tag) extends Table[Company](tag, "COMPANY") {
def id = column[Long]("ID", O.AutoInc, O.PrimaryKey)
def name = column[String]("NAME")
def * = (id.?, name) <> (Company.tupled, Company.unapply)
}
val companies = TableQuery[Companies]
class Departments(tag: Tag) extends Table[Department](tag, "DEPARTMENT") {
def id = column[Long]("ID", O.AutoInc, O.PrimaryKey)
def name = column[String]("NAME")
def companyId = column[Long]("COMPANY_ID")
def company = foreignKey("FK_DEPARTMENT_COMPANY", companyId, companies)(_.id)
def * = (id.?, name, companyId) <> (Department.tupled, Department.unapply)
}
val departments = TableQuery[Departments]
and here's my method to query all companies with all related departments:
override def findAll: Future[List[(Company, Department)]] = {
db.run((companies join departments on (_.id === _.companyId)).to[List].result)
}
Unfortuneatelly I want to display data in tree JSON format, so I will have to build query that gets all companies with departments and map them somehow to CompanyDTO, something like that:
case class CompanyDTO(id: Option[Long], name: String, departments: List[Department])
Do you know what is the best solution for this? Should I map List[(Company, Department)] with JSON formatters or should I change my query to use CompanyDTO? If so how can I map results to CompanyDTO?
One-to-Many relationships to my knowledge haven't been known to be fetched in one query in RDBMS. the best you can do while avoiding the n+1 problem is doing it in 2 queries. here's how it'd go in your case:
for {
comps <- companies.result
deps <- comps.map(c => departments.filter(_.companyId === c.id.get))
.reduceLeft((carry,item) => carry unionAll item).result
grouped = deps.groupBy(_.companyId)
} yield comps.map{ c =>
val companyDeps = grouped.getOrElse(c.id.get,Seq()).toList
CompanyDTO(c.id,c.name,companyDeps)
}
there are some fixed parts in this query that you'll come to realize in time. this makes it a good candidate for abstraction, something you can reuse to fetch one-to-many relationships in general in slick.
I have the following model:
case class ProcessStepTemplatesModel(
id: Option[Int],
title: String,
createdat: String,
updatedat: String,
deadline: Option[Date],
comment: Option[String],
stepType: Int,
deleted: Boolean,
processtemplate: Option[Int])
object ProcessStepTemplatesModel {
implicit val processStepFormat = Json.format[ProcessStepTemplatesModel]
}
I have an extra value derived. All data is sent via POST as JSON to my controller. When I validate the request with the model above this value is lost.
I need this value for the model to work with, but it shouldn't be persisted.
But if I add the value to the model I get an error from Scala slick.
Update:
From the top of my head, two options for you:
1. Decouple your frontend representation from your actual model
You could have a ProcessStepTemplatesClientModel, which has the extra field derived and is only used to validate the JSON input in the controller. After you are done with your business logic involving the derived field you convert the object to ProcessStepTemplatesModel and persist it in your database.
2. Handle the field in your *-projection of your Slick table
Include the derived field in your ProcessStepTemplatesModel class (assuming it is boolean, works with any other primitive):
case class ProcessStepTemplatesModel(
id: Option[Int],
title: String,
createdat: String,
updatedat: String,
deadline: Option[Date],
comment: Option[String],
stepType: Int,
deleted: Boolean,
processtemplate: Option[Int],
derived: Boolean)
And since you are using Slick as your database mapper you probably have a table representation for your ProcessStepTemplatesModel:
class ProcessStepTemplatesModelTable(tag: Tag) extends Table[ProcessTableTemplatesModel](tag, "PROCESS_TABLE_TEMPLATES_MODEL") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
...
def processtemplate = column[Option[Int]]("PROCESSTEMPLATE")
def * = (id, ..., processtemplate) <> ( {
tuple: (Int, ..., Option[Int]) => ProcessStepTemplatesModel(tuple._1, ..., tuple._9, derived = false)
}, {
ps: ProcessStepTemplatesModel => Some((ps.id, ..., ps.processtemplate))
})
}
Don't include the derived field in the table definition and handle that case within the *-projection by handing a static value to the case class constructor to create the object from the tuple and just leave it out when creating the tuple from the object.
EDIT
As a response to your comment, a more concrete implementation of the *-projection, based on the ProcessStepTemplatesModel including derived:
def * : ProvenShape[ProcessStepTemplatesModel] = (id.?, title, createdat, updatedat, deadline, comment, stepType, deleted, processtemplate) <> ( {
tuple: (Option[Int], String, String, String, Option[Data], Option[String], Int, Boolean, Option[Int]) => ProcessStepTemplatesModel(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, derived = false)
}, {
ps: ProcessStepTemplatesModel => Some((ps.id, ps.title, ps.createdat, ps.updatedat, ps.deadline, ps.comment, ps.stepType, ps.deleted, ps.processtemplate))
})
I am working in a project with scala play 2 framework where i am using slick as FRM and postgres database.
In my project customer is an entity. So i create a customer table and customer case class and object also. Another entity is account. So i create a account table and account case class and object also. The code is given bellow
case class Customer(id: Option[Int],
status: String,
balance: Double,
payable: Double,
created: Option[Instant],
updated: Option[Instant]) extends GenericEntity {
def this(status: String,
balance: Double,
payable: Double) = this(None, status, balance, payable, None, None)
}
class CustomerTable(tag: Tag) extends GenericTable[Customer](tag, "customer"){
override def id = column[Option[Int]]("id")
def status = column[String]("status")
def balance = column[Double]("balance")
def payable = column[Double]("payable")
def account = foreignKey("fk_customer_account", id, Accounts.table)(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * = (id, status, balance, payable, created, updated) <> ((Customer.apply _).tupled, Customer.unapply)
}
object Customers extends GenericService[Customer, CustomerTable] {
override val table = TableQuery[CustomerTable]
val accountTable = TableQuery[AccountTable]
override def copyEntityFields(entity: Customer, id: Option[Int],
created: Option[Instant], updated: Option[Instant]): Customer = {
entity.copy(id = id, created = created, updated = updated)
}
}
Now I Want to join Customer Table and Account Table and map the result to a case class named CustomerWithAccount
I have tried the following code
case class CustomerDetail(id: Option[Int],
status: String,
name: String)
object Customers extends GenericService[Customer, CustomerTable] {
override val table = TableQuery[CustomerTable]
val accountTable = TableQuery[AccountTable]
def getAllCustomersWithAccount = db.run(table.join(accountTable).on(_.id === _.id).map { row =>
//for (row1 <- row) {
for {
id <- row._1.id
status <- row._1.status.toString()
name <- row._2.name.toString()
} yield CustomerDetail(id = id, status = status, name = name)
//}
}.result)
override def copyEntityFields(entity: Customer, id: Option[Int], created:Option[Instant], updated: Option[Instant]): Customer = {
entity.copy(id = id, created = created, updated = updated)
}
}
But this did not work.
Please help me.
You can try this query
db.run((table.join(accountTable).on(_.id === _.id)
.map{
case (t,a) => ((t.id, t.status, a.name) <> (CustomerDetail.tupled, CustomerDetail.unapply _))
}).result)
you can do this with 3 case classes, 1 per table and then 1 for the joined result.
db.run((customerTable.join(accountTable).on(_.id === _.id)
.map{
case (c,a) => CustomerWithAccount(status = c.status, created =
c.created, account=a, ...)
}
Suppose, I have my domain object named "Office":
case class Office(
id: Long,
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
null.asInstanceOf[Long], name, phone, address
)
}
When I create new Office:
new Office("officeName","00000000000", "officeAddress")
I don't specify id field becouse I don't know it. When I save office (by Anorm) I now id and do that:
office.id = officeId
So. I know that using null is non-Scala way. How to avoid using null in my case?
UPDATE #1
Using Option.
Suppose, something like this:
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
And, after saving:
office.id = Option(officeId)
But what if I need to find something by office id?
SomeService.findSomethingByOfficeId(office.id.get)
Does it clear? office.id.get looks not so good)
UPDATE #2
Everyone thanks! I've got new ideas from your answers! Greate thanks!
Why not declare the id field as a Option? You should really avoid using null in Scala. Option is preferable since it is type-safe and plays nice with other constructs in the functional paradigm.
Something like (I haven't tested this code):
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
Just make the id field an Option[Long]; once you have that, you can use it like this:
office.id.map(SomeService.findSomethingByOfficeId)
This will do what you want and return Option[Something]. If office.id is None, map() won't even invoke the finder method and will immediately return None, which is what you want typically.
And if findSomethingByOfficeId returns Option[Something] (which it should) instead of just Something or null/exception, use:
office.id.flatMap(SomeService.findSomethingByOfficeId)
This way, if office.id is None, it will, again, immediately return None; however, if it's Some(123), it will pass that 123 into findSomethingByOfficeId; now if the finder returns a Some(something) it will return Some(something), if however the finder returns None, it will again return None.
if findSomethingByOfficeId can return null and you can't change its source code, wrap any calls to it with Option(...)—that will convert nulls to None and wrap any other values in Some(...); if it can throw an exception when it can't find the something, wrap calls to it with Try(...).toOption to get the same effect (although this will also convert any unrelated exceptions to None, which is probably undesirable, but which you can fix with recoverWith).
The general guideline is always avoid null and exceptions in Scala code (as you stated); always prefer Option[T] with either map or flatMap chaining, or using the monadic for syntactic sugar hiding the use of map and flatMap.
Runnable example:
object OptionDemo extends App {
case class Something(name: String)
case class Office(id: Option[Long])
def findSomethingByOfficeId(officeId: Long) = {
if (officeId == 123) Some(Something("London")) else None
}
val office1 = Office(id = None)
val office2 = Office(id = Some(456))
val office3 = Office(id = Some(123))
println(office1.id.flatMap(findSomethingByOfficeId))
println(office2.id.flatMap(findSomethingByOfficeId))
println(office3.id.flatMap(findSomethingByOfficeId))
}
Output:
None
None
Some(Something(London))
For a great introduction to Scala's rather useful Option[T] type, see http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html.
When using id: Option[Long] , extract the option value for instance with
if (office.id.isDefined) {
val Some(id) = office.id
SomeService.findSomethingByOfficeId(id)
}
or perhaps for instance
office.id match {
case None => Array()
case Some(id) => SomeService.findSomethingByOfficeId(id)
}
Also you can define case classes and objects as follows,
trait OId
case object NoId extends OId
case class Id(value: Long) extends OId
case class Office (
id: OId = NoId,
name: String,
phone: String,
address: String
)
Note that by defaulting id for example to NoId , there is no need to declare a call to this. Then
val office = Office (Id(123), "name","phone","addr")
val officeNoId = Office (name = "name",phone="phone",address="addr")
If the id member is defined last, then there is no need to name the member names,
val office = Office ("name","phone","addr")
office: Office = Office(name,phone,addr,NoId)
As of invoking (neatly) a method,
office.id match {
case NoId => Array()
case Id(value) => SomeService.findSomethingByOfficeId(value)
}
I prefer more strong restriction for object Id property:
trait Id[+T] {
class ObjectHasNoIdExcepthion extends Throwable
def id : T = throw new ObjectHasNoIdExcepthion
}
case class Office(
name: String,
phone: String,
address: String
) extends Id[Long]
object Office {
def apply(_id : Long, name: String, phone: String, address: String) =
new Office(name, phone, address) {
override def id : Long = _id
}
}
And if I try to get Id for object what is not stored in DB, I get exception and this mean that something wrong in program behavior.
val officeWithoutId =
Office("officeName","00000000000", "officeAddress")
officeWithoutId.id // Throw exception
// for-comprehension and Try monad can be used
// update office:
for {
id <- Try { officeWithoutId.id }
newOffice = officeWithoutId.copy(name = "newOffice")
} yield OfficeDAL.update(id, newOffice)
// find office by id:
for {
id <- Try { officeWithoutId.id }
} yield OfficeDAL.findById(id)
val officeWithId =
Office(1000L, "officeName","00000000000", "officeAddress")
officeWithId.id // Ok
Pros:
1) method apply with id parameter can be incapsulated in DAL logic
private[dal] def apply (_id : Long, name: String, ...
2) copy method always create new object with empty id (safty if you change data)
3) update method is safety (object not be overridden by default, id always need to be specified)
Cons:
1) Special serealization/deserealization logic needed for store id property (json for webservices, etc)
P.S.
this approach is good if you have immutable object (ADT) and store it to DB with id + object version instead object replace.
How does one insert records into PostgreSQL using AutoInc keys with Slick mapped tables? If I use and Option for the id in my case class and set it to None, then PostgreSQL will complain on insert that the field cannot be null. This works for H2, but not for PostgreSQL:
//import scala.slick.driver.H2Driver.simple._
//import scala.slick.driver.BasicProfile.SimpleQL.Table
import scala.slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
object TestMappedTable extends App{
case class User(id: Option[Int], first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id.? ~ first ~ last <> (User, User.unapply _)
def ins1 = first ~ last returning id
val findByID = createFinderBy(_.id)
def autoInc = id.? ~ first ~ last <> (User, User.unapply _) returning id
}
// implicit val session = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver").createSession()
implicit val session = Database.forURL("jdbc:postgresql:test:slicktest",
driver="org.postgresql.Driver",
user="postgres",
password="xxx")
session.withTransaction{
Users.ddl.create
// insert data
print(Users.insert(User(None, "Jack", "Green" )))
print(Users.insert(User(None, "Joe", "Blue" )))
print(Users.insert(User(None, "John", "Purple" )))
val u = Users.insert(User(None, "Jim", "Yellow" ))
// println(u.id.get)
print(Users.autoInc.insert(User(None, "Johnathan", "Seagul" )))
}
session.withTransaction{
val queryUsers = for {
user <- Users
} yield (user.id, user.first)
println(queryUsers.list)
Users.where(_.id between(1, 2)).foreach(println)
println("ID 3 -> " + Users.findByID.first(3))
}
}
Using the above with H2 succeeds, but if I comment it out and change to PostgreSQL, then I get:
[error] (run-main) org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
This is working here:
object Application extends Table[(Long, String)]("application") {
def idlApplication = column[Long]("idlapplication", O.PrimaryKey, O.AutoInc)
def appName = column[String]("appname")
def * = idlApplication ~ appName
def autoInc = appName returning idlApplication
}
var id = Application.autoInc.insert("App1")
This is how my SQL looks:
CREATE TABLE application
(idlapplication BIGSERIAL PRIMARY KEY,
appName VARCHAR(500));
Update:
The specific problem with regard to a mapped table with User (as in the question) can be solved as follows:
def forInsert = first ~ last <>
({ (f, l) => User(None, f, l) }, { u:User => Some((u.first, u.last)) })
This is from the test cases in the Slick git repository.
I tackled this problem in an different way. Since I expect my User objects to always have an id in my application logic and the only point where one would not have it is during the insertion to the database, I use an auxiliary NewUser case class which doesn't have an id.
case class User(id: Int, first: String, last: String)
case class NewUser(first: String, last: String)
object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id ~ first ~ last <> (User, User.unapply _)
def autoInc = first ~ last <> (NewUser, NewUser.unapply _) returning id
}
val id = Users.autoInc.insert(NewUser("John", "Doe"))
Again, User maps 1:1 to the database entry/row while NewUser could be replaced by a tuple if you wanted to avoid having the extra case class, since it is only used as a data container for the insert invocation.
EDIT:
If you want more safety (with somewhat increased verbosity) you can make use of a trait for the case classes like so:
trait UserT {
def first: String
def last: String
}
case class User(id: Int, first: String, last: String) extends UserT
case class NewUser(first: String, last: String) extends UserT
// ... the rest remains intact
In this case you would apply your model changes to the trait first (including any mixins you might need), and optionally add default values to the NewUser.
Author's opinion: I still prefer the no-trait solution as it is more compact and changes to the model are a matter of copy-pasting the User params and then removing the id (auto-inc primary key), both in case class declaration and in table projections.
We're using a slightly different approach. Instead of creating a further projection, we request the next id for a table, copy it into the case class and use the default projection '*' for inserting the table entry.
For postgres it looks like this:
Let your Table-Objects implement this trait
trait TableWithId { this: Table[_] =>
/**
* can be overriden if the plural of tablename is irregular
**/
val idColName: String = s"${tableName.dropRight(1)}_id"
def id = column[Int](s"${idColName}", O.PrimaryKey, O.AutoInc)
def getNextId = (Q[Int] + s"""select nextval('"${tableName}_${idColName}_seq"')""").first
}
All your entity case classes need a method like this (should also be defined in a trait):
case class Entity (...) {
def withId(newId: Id): Entity = this.copy(id = Some(newId)
}
New entities can now be inserted this way:
object Entities extends Table[Entity]("entities") with TableWithId {
override val idColName: String = "entity_id"
...
def save(entity: Entity) = this insert entity.withId(getNextId)
}
The code is still not DRY, because you need to define the withId method for each table. Furthermore you have to request the next id before you insert an entity which might lead to performance impacts, but shouldn't be notable unless you insert thousands of entries at a time.
The main advantage is that there is no need for a second projection what makes the code less error prone, in particular for tables having many columns.
The simplest solution was to use the SERIAL type like this:
def id = column[Long]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
Here's a more concrete block:
// A case class to be used as table map
case class CaseTable( id: Long = 0L, dataType: String, strBlob: String)
// Class for our Table
class MyTable(tag: Tag) extends Table[CaseTable](tag, "mytable") {
// Define the columns
def dataType = column[String]("datatype")
def strBlob = column[String]("strblob")
// Auto Increment the id primary key column
def id = column[Long]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
// the * projection (e.g. select * ...) auto-transforms the tupled column values
def * = (id, dataType, strBlob) <> (CaseTable.tupled, CaseTable.unapply _)
}
// Insert and get auto incremented primary key
def insertData(dataType: String, strBlob: String, id: Long = 0L): Long = {
// DB Connection
val db = Database.forURL(jdbcUrl, pgUser, pgPassword, driver = driverClass)
// Variable to run queries on our table
val myTable = TableQuery[MyTable]
val insert = try {
// Form the query
val query = myTable returning myTable.map(_.id) += CaseTable(id, dataType, strBlob)
// Execute it and wait for result
val autoId = Await.result(db.run(query), maxWaitMins)
// Return ID
autoId
}
catch {
case e: Exception => {
logger.error("Error in inserting using Slick: ", e.getMessage)
e.printStackTrace()
-1L
}
}
insert
}
I've faced the same problem trying to make the computer-database sample from play-slick-3.0 when I changed the db to Postgres. What solved the problem was to change the id column (primary key) type to SERIAL in the evolution file /conf/evolutions/default/1.sql (originally was in BIGINT). Take a look at https://groups.google.com/forum/?fromgroups=#%21topic/scalaquery/OEOF8HNzn2U
for the whole discussion.
Cheers,
ReneX
Another trick is making the id of the case class a var
case class Entity(var id: Long)
To insert an instance, create it like below
Entity(null.asInstanceOf[Long])
I've tested that it works.
The solution I've found is to use SqlType("Serial") in the column definition. I haven't tested it extensively yet, but it seems to work so far.
So instead of
def id: Rep[PK[SomeTable]] = column[PK[SomeTable]]("id", O.PrimaryKey, O.AutoInc)
You should do:
def id: Rep[PK[SomeTable]] = column[PK[SomeTable]]("id", SqlType("SERIAL"), O.PrimaryKey, O.AutoInc)
Where PK is defined like the example in the "Essential Slick" book:
final case class PK[A](value: Long = 0L) extends AnyVal with MappedTo[Long]