Scala Slick Model with Optional Image column - scala

I am trying to add a column to an existing model that has an optional image, I am using Scala Slick with the Play Framework. What would be an example of storing a image with a Scala Slick model?
case class Complication(complicationId:Long,
vitalSignId:Long,
complication:String,definition:String,reason:String,
treatment:String,notes:String,
weblinks:String,upperlower:String)
object ComplicationDAO extends Table[Complication]("complications") with DbConn {
def complicationId = column[Long]("complication_id", O.PrimaryKey, O.AutoInc)
def vitalSignId = column[Long]("vital_sign_id")
def complication = column[String]("complication")
def definition = column[String]("definition",O.DBType("varchar(4000)"))
def reason = column[String]("reason",O.DBType("varchar(4000)"))
def treatment = column[String]("treatment",O.DBType("varchar(4000)"))
def notes = column[String]("notes",O.DBType("varchar(4000)"))
def weblinks = column[String]("weblinks")
def upperlower = column[String]("upperlower")
def treatmentImage = ??? //here is where it needs to be defined!
def * = complicationId ~ vitalSignId ~ complication ~ definition ~ reason ~ treatment ~ notes ~ weblinks ~ upperlower<> (Complication, Complication.unapply _)
def autoInc =
vitalSignId ~ complication ~ definition ~ reason ~
treatment ~ notes ~ weblinks ~ upperlower <>
(NewComplication, NewComplication.unapply _) returning complicationId
}
This is the model that I am trying to integrate an image into.
Thanks for the advice!

If you don't want to actually store it into the DB you can use a url as String field, this example can get you started.
A table for pictures is used:
case class Picture(url: String, id: Option[Int] = None)
trait PictureComponent { this: Profile => //requires a Profile to be mixed in...
import profile.simple._ //...to be able import profile.simple._
class Pictures(tag: Tag) extends Table[Picture](tag, "PICTURES") {
def id = column[Option[Int]]("PIC_ID", O.PrimaryKey, O.AutoInc)
def url = column[String]("PIC_URL", O.NotNull)
def * = (url, id) <> (Picture.tupled, Picture.unapply)
}
val pictures = TableQuery[Pictures]
private val picturesAutoInc = pictures returning pictures.map(_.id) into { case (p, id) => p.copy(id = id) }
def insert(picture: Picture)(implicit session: Session): Picture = picturesAutoInc.insert(picture)
}
And then the user table as an external reference to the picture id:
class Users(tag: Tag) extends Table[(String, Int, Option[Int])](tag, "USERS") {
def id = column[Option[Int]]("USER_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("USER_NAME", O.NotNull)
def pictureId = column[Int]("PIC_ID", O.NotNull)
def * = (name, pictureId, id)
}
When inserting you can upload the image, store the url, retrieve the id and assign it to the User entity as external key. To make the external key column nullable you just have to use [column[Option[Int]] instead of column[Int].
Otherwise you could use a Blob field as suggested in a comment, there's a test inside the Slick library with JDBC that creates a table with a Blob field:
class T(tag: Tag) extends Table[(Int, Blob)](tag, "test3") {
def id = column[Int]("id")
def data = column[Blob]("data")
def * = (id, data)
}
val ts = TableQuery[T]
And then to insert:
ts insert (1, new SerialBlob(Array[Byte](1,2,3)))
ts insert (2, new SerialBlob(Array[Byte](4,5)))
Then you can look into java.io.serializable on how to transform a file to bites before storing it into a Blob, this link could help (ignore the sql parts).

Related

How to use select statement using slick?

I wanted to query data from example table like below using slick
| id | username | password |
+-----+----------+------------+
| 1 | admin | admin#123 |
| 2 | user | user#123 |
The query is
SELECT password FROM users WHERE username = 'user';
I have read many slick examples on stack overflow but they are more complex queries
I want a simple query as above using slick .
You can just use plain SQL query for such cases:
sql"""SELECT password FROM users WHERE username = 'user'""".as[String]
This is less portable than building queries, but let you write any arbitrary query and run it without the risk of SQL injection. Though if you are using only such queries then you don't have a reason to use Slick in the first place - you could rather use Quill or Doobie (or both) or ScalikeJDBC or jOOQ.
Using slick you can write your implementation like below:
trait UsertDAO {
this: DbComponent =>
import driver.api._
case class User(id: Int, username: String, password: String)
val userTableQuery = TableQuery[UserTable]
class UserTable(tag: Tag) extends Table[User](tag, "user") {
val id = column[String]("id", O.SqlType("NUMBER"))
val username = column[String]("username", O.SqlType("VARCHAR(200)"))
val password = column[String]("password", O.SqlType("VARCHAR(200)"))
def pk: PrimaryKey = primaryKey("pk", id)
def * : ProvenShape[CjTable] =
(id, username, password) <> (User.tupled, User.unapply)
}
}
The above class defines your table structure. Later you can define your method like to run the sql query. :
def getPassword(userName: String): Future[Option[String]] = {
db.run{
userTableQuery.filter(_.username === userName).map(_.password).to[List].result.headOption
}
}
If you still face any issues, Try looking at the CRUD application i made: https://github.com/SangeetaGulia/Student-CRUD-Slick/blob/master/Student-Slick-Project.zip
First define a class that represents your SQL table
class UsersTable(tag: Tag) extends Table[User](tag, "users") {
def email: Rep[String] = column[String]("email", O.PrimaryKey)
def password: Rep[String] = column[String]("password")
def registered: Rep[Boolean] = column[Boolean]("registered")
def firstName: Rep[Option[String]] = column[Option[String]]("first_name")
def lastName: Rep[Option[String]] = column[Option[String]]("last_name")
def username: Rep[Option[String]] = column[Option[String]]("username", O.Unique)
def contact: Rep[Option[String]] = column[Option[String]]("contact")
override def * = (email, password, registered, firstName, lastName, username, contact) <> (User.tupled, User.unapply)
}
Then create a DAO for your table which has the method to select 'one' row. Like this:
class UsersDao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
(implicit executionContext: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private val Users = TableQuery[UsersTable]
def selectOne(email: String): Future[Option[User]] = db.run(Users.filter(_.email === email).result.headOption)
}
Now in your service class read the record like this
class AuthService #Inject()(usersDao: UsersDao) {
def someFunction(.. some_params ..) {
usersDao.selectOne(someParama.email).flatMap {
case Some(user: User) => //Do something if user exists
case None => //Do something else if user doesn't exist
}
}
}
In case you're interested in exploring a play project written in Scala, have a look at this project https://github.com/ashishtomer/fastscraping-web

How to omit column values when doing a bulk-insert slick 3.x?

I have a JOURNAL table where the INSERT_DATE column should be filled by the DB with the current date and time when the record is inserted. I did not use the TIMESTAMP type on purpose, because of its limited range.
class Journal(tag: Tag) extends Table[JournalEntry](tag, "JOURNAL") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def insertDate = column[OffsetDateTime]("INSERT_DATE", SqlType("DateTime default CURRENT_TIMESTAMP"))(localDateTimeColumnType)
def valueDate = column[OffsetDateTime]("VALUE_DATE", SqlType("DateTime"))(localDateTimeColumnType)
def amount = column[Int]("AMOUNT")
def note = column[String]("NOTE", O.Length(100))
def * : ProvenShape[JournalEntry] = (id.?, insertDate.?, valueDate, amount, note)
<> ((JournalEntry.apply _).tupled, JournalEntry.unapply)
}
I also implement a case class:
case class JournalEntry(id: Option[Int], insertDate: Option[LocalDateTime],
valueDate: LocalDateTime, amount: Int, note: String)
When my app starts up, I populate the DB with random test data:
TableQuery[Journal] ++= Seq.fill(1000)(JournalEntry(None, Some(LocalDateTime.now()),
LocalDateTime.of(2006 + Random.nextInt(10), 1 + Random.nextInt(11),
1 + Random.nextInt(27),Random.nextInt(24), Random.nextInt(60)), Random.nextInt(),
TestDatabase.randomString(100)))
This works, but the INSERT_DATE ist set by the JVM not by the Database. The Slick docs say that columns should be omitted, if one wants the default value to get inserted. But I just dont get how I omit columns if I have a case class.
I also found this SO post but could not figure out how to use it in my context.
Any ideas?
The Slick docs give an example of such omission right in the first code snippet here. Follow the steps or the cvogt's answer and you will arrive at the solution:
TableQuery[Journal].map(je => (je.id, je.valueDate, je.amount, je.note)) ++= Seq.fill(1000)((None, LocalDateTime.of(2006 + Random.nextInt(10), 1 + Random.nextInt(11), 1 + Random.nextInt(27),Random.nextInt(24), Random.nextInt(60)), Random.nextInt(), TestDatabase.randomString(100)))
I work in the following way:
import java.time.{ ZonedDateTime, ZoneOffset}
import slick.profile.SqlProfile.ColumnOption.SqlType
import scala.concurrent.duration.Duration
import scala.concurrent.Await
implicit val zonedDateTimeType = MappedColumnType.base[ZonedDateTime, Timestamp](
{dt =>Timestamp.from(dt.toInstant)},
{ts =>ZonedDateTime.ofInstant(ts.toInstant, ZoneOffset.UTC)}
)
class Users(tag: Tag) extends Table[(String, ZonedDateTime)](tag, "users") {
def name = column[String]("name")
def createAt = column[ZonedDateTime]("create_at", SqlType("timestamp not null default CURRENT_TIMESTAMP"))
def * = (name, createAt)
}
val users = TableQuery[Users]
val setup = DBIO.seq(
users.schema.create,
users.map(u => (u.name)) ++= Seq(("Amy"), ("Bob"), ("Chris"), ("Dave"))
Await.result(db.run(setup), Duration.Inf)
I am not using case class here, just a tuple.

Play Framework and Slick: testing database-related services

I'm trying to follow the most idiomatic way to having a few fully tested DAO services.
I've got a few case classes such as the following:
case class Person (
id :Int,
firstName :String,
lastName :String
)
case class Car (
id :Int,
brand :String,
model :String
)
Then I've got a simple DAO class like this:
class ADao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val persons = TableQuery[PersonTable]
private val cars = TableQuery[CarTable]
private val personCar = TableQuery[PersonCar]
class PersonTable(tag: Tag) extends Table[Person](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("name")
def lastName = column[String]("description")
def * = (id, firstName, lastName) <> (Person.tupled, Person.unapply)
}
class CarTable(tag: Tag) extends Table[Car](tag, "car") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def brand = column[String]("brand")
def model = column[String]("model")
def * = (id, brand, model) <> (Car.tupled, Car.unapply)
}
// relationship
class PersonCar(tag: Tag) extends Table[(Int, Int)](tag, "person_car") {
def carId = column[Int]("c_id")
def personId = column[Int]("p_id")
def * = (carId, personId)
}
// simple function that I want to test
def getAll(): Future[Seq[((Person, (Int, Int)), Car)]] = db.run(
persons
.join(personCar).on(_.id === _.personId)
.join(cars).on(_._2.carId === _.id)
.result
)
}
And my application.conf looks like:
slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://super-secrete-prod-host/my-awesome-db"
slick.dbs.default.db.user="myself"
slick.dbs.default.db.password="yolo"
Now by going through Testing with databases and trying to mimic play-slick sample project
I'm getting into so much trouble and I cannot seem to understand how to make my test use a different database (I suppose I need to add a different db on my conf file, say slick.dbs.test) but then I couldn't find out how to inject that inside the test.
Also, on the sample repo, there's some "magic" like Application.instanceCache[CatDAO] or app2dao(app).
Can anybody point me at some full fledged example of or repo that deals correctly with testing play and slick?
Thanks.
I agree it's confusing. I don't know if this is the best solution, but I ended up having a separate configuration file test.conf that specifies an in-memory database:
slick.dbs {
default {
driver = "slick.driver.H2Driver$"
db.driver = "org.h2.Driver"
db.url = "jdbc:h2:mem:play-test"
}
}
and then told sbt to use this when running the tests:
[..] javaOptions in Test ++= Seq("-Dconfig.file=conf/test.conf")

Scala & Play! & Slick & PostgreSQL auto increment

I have the following code in Scala:
case class Product(id: Option[Long] = None, name: String, price: BigDecimal, description: String)
object Products extends Table[Product]("product") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name", O.NotNull)
def price = column[BigDecimal]("price", O.NotNull)
def description = column[String]("description", O.NotNull)
def * = id.? ~ name ~ price ~ description <>(Product.apply _, Product.unapply _)
def autoInc = * returning id
def add(product: Product)(implicit s:Session): Long = {
Products.autoInc.insert(product)
}
def all(implicit s:Session): List[Product] = {
Query(Products).list
}
}
Listing all products works great, however, I can't make adding method working.
After calling:
val myProduct = models.Product(id = None, name = "test2", price = BigDecimal(2.99), description = "test3")
models.Products.add(myProduct)
I constanty get an error message from PostgreSQL saying that id cannot be null. I totally agree with that, but why the id column is not being set by autoInc? Doesn't it work that way?
I use Play! 2.1.2, Scala 2.10.0, PostgreSQL 9.3 and play-slick 0.3.3.
Thanks in advance.
Here is a suggestion, rewrite your autoInc and add methods like this:
def autoInc = name ~ price ~ description returning id
def add(product: Product)(implicit s:Session): Long = {
Products.autoInc.insert(p.name, p.price, p.description)
}
Some databases own't allow you to insert null in the auto increment column. Maybe it's the Postgres case.

Using Projection to define list of columns to select in Slick

I have table definition in Slick:
object ADB {
extends BaseDB[A]("a")
with PostgresDriver{
def id = column[Long]("id", O.PrimaryKey)
def name = column[String]("name")
...
def * = id ~ name ~ ... <> (A.apply _, A.unapply _)
def forSelect = id ~ name
}
Is it possible to refer to forSelect when querying for A?
I want to keep the list of field to be selected in one place to be able to push forSelect to trait in future.
I believe you can accomplish what you want like this:
( for( a <- ADB ) yield a.forSelect ).list
The difference between this and what stefan.schwetschke posted is that I'm using the instance a to reference forSelect instead of accessing it from the ADB object itself.
Just try
...map(ADB.forSelect)
or
for( ... ) yield ADB.forSelect
The following worked for me:
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
object ADB extends Table[(Long, String)]("a") {
def id = column[Long]("id", O.PrimaryKey)
def name = column[String]("name")
def * = id ~ name
def forSelect = id ~ name
}
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
( for( a <- ADB ) yield ADB.forSelect ).list
}