How to Map One-To-Many Relation to JSON Model in Slick - json

I have 2 classes like this
case class Employee(id: Long,
name: String,
prefix: Option[String],
role: String)
case class Report(id: Long,
name: String,
employee_id: Long)
expecting JSON format
{
"id":1,
"name":"employee",
"prefix":"emp",
"role":"eng",
"reports":[
{
"id":1,
"name":"report_1"
},
{
"id":2,
"name":"report_2"
}
]
}
an employee can have many reports (one-to-many) and i have tables connected with foreign key my questions in which way to retrieve data from database i tried inner join like this
val query: PostgresProfile.api.type#TableQuery[T]
def getAllQuery = {
query.filter(_.isDeleted === false)
}
================================================
def getAllEmployee: Future[Seq[Employee]] = {
val joinQuery = super.getAllQuery.join(reportRepo.getAllQuery).on(_.id === _.employee_id)
val joinRes: Future[Seq[(Employee, Report)]] = db.run(joinQuery.result)
joinRes map { tupleList =>
tupleList.map { tuple =>
Employee(tuple._1.id, tuple._1.name, tuple._1.prefix, tuple._1.role)
}
}
}
i received the data and when i tried to map to JSON it comes with duplicate values too. can anyone suggest way to map or better way to retrieve data from DB with an example.
sorry if i made any mistakes i'm new to playframework and thanks is advance.

The thing which you are looking is groupBy, I guess.
You fetch the data by your join query and groupBy on the employee's ID.
val query = employee.filter(_.yourField === yourCondition).join(report).on(_.id === _.employee_id)
val joinRes = db.run(query.to[List].result)
joinRes.map { list =>
list.groupBy {
case (emp, report) => emp.id
}
}
or
joinRes.map(_.groupBy(_._1.id))
Now
where employee is TableQuery object of employee and report is TableQuery object of report.
Give it a try :)

To get desired json you need to map result into appropriate case class.
case class EmployeeWithReports(id: Long,
name: String,
prefix: Option[String],
role: String,
reports: List[Report])
object EmployeeWithReports {
implicit val jsonFormat = Json.format[EmployeeWithReports]
}
Mapping to this case class can be done in DBIOAction. Firstly group result by employee id and than map all map entries (Int, Seq[Employee, Report]) to EmployeeWithReports
val query = super.getAllQuery
.join(reportRepo.getAllQuery)
.on(_.id === _.employee_id)
val action = query.result.map(rows => {
rows.groupBy(_._1.id).map { mapEntry =>
val (id, name, prefix, role) = mapEntry._2.head._1
EmployeeWithReports(id, name, prefix, role, mapEntry._2.flatMap(_._2).toList)
}.toList
})
Then you can get result calling
val employees: Future[List[EmployeeWithReports]] = db.run(action)

Related

Failing to use transactions in Quill to INSERT one-to-many relational objects

I have a person table and animal table and in the animal table there is FK to personId since there is one-to-many relation between them.
I just want to create a person and create its animals using a transaction cause I want the process to be atomic (there is no use of person in the db if I could not create its animals)
This is the model of how I accept a person creation request:
case class PersonCreateRequest(name: String, age: Int, animals: Seq[AnimalCreateRequest])
This is how the DB knows a Person:
case class Person(personId: Long, name, age: Int)
// this is just a companion object to help me take a PersonCreateRequest and make it Person
object Person {
def apply(person: PersonCreateRequest): Person = {
Person(0L,
person.name,
person.age)
}
}
same thing I have with Animal:
case class AnimalCreateRequest(animalType: String, age: Int)
This is how the db knows a Animal(personId = owner):
case class Animal(animalId: Long, animalType: String, age: Int, personId: Long)
// here I need to get personId as parameter cause I will only have it after a person was created:
object Animal {
def apply(animal: AnimalCreateRequest, personId: Long): Animal = {
Animal(0L,
animal.animalType,
animal.age,
personId)
}
}
So now this is how I tried to do it(and failed):
lazy val ctx = new MysqlAsyncContext(CamelCase, "ctx")
import ctx._
def insertPerson(personToCreate: PersonCreateRequest): Future[Long] = {
// getting the person object that the db knows
val dbPerson = Person.apply(personToCreate)
// INSERT Person Query
val insertPersonQuery = quote {
query[Person].insert(lift(dbPerson)).returning(_.personId)
}
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
contactIds <- {
Future.sequence(
personToCreate.animals.map(animal => {
val animalToInsert = Animal.apply(animal, personId)
insertAnimal(animalToInsert)
})
)
}
} yield personId
}
}
def insertAnimal(animal: Animal): Future[Long] = {
val q = quote {
query[Animal].insert(lift(animal)).returning(_.animalId)
}
ctx.run(q)
}
What happens is that I just dont get a response...its keep processing without returning anything or throwing an error
Problem was, currently, Quill async does not support concurrent operations inside transactions.
So had to do the animal insertion sequentially:
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
animals = personCreate.animals.map(Animal.apply(personId, _))
_ <- animals.foldLeft(Future.successful(0l)) {
case (fut, animal) =>
fut.flatMap(_ => insertAnimal(animal))
}
} yield personId
}
also, even better is to use batch insertion :)
Thanks for #fwbrasil and #mentegy for the assistance!
Add implicit ExecutionContext parameter to insertAnimal method:
def insertAnimal(animal: Animal)(implicit ec: ExecutionContext): Future[Long] =
Without it, you're not passing ec from the transaction block and animal insertions will try and use other connections from the pool.
Are you familiar with Scala Futures?
To get a result from a transaction you should add onSuccess handler to a Future returned from the ctx.transaction call:
ctx.transaction { ...
}.onSuccess {
case personId => ...
}

How to return json from Play Scala controller?

I would like to know that how can I return json response data from Play(2.2.x) Scala controller class to display on my view page ? I have json objects in Postgresql database(table name: "test" and having: id and name). Please provide me any solutions for it.
I have tried the following cases(a and b), but I am not sure why I am not getting the response(like: names) on my controller, so I can show them on my view page ? since I am very new to Play/Scala and Postgresql.
case a. If I give like:
model:
def getTestValuesFromTable() = {
DB.withConnection { implicit connection =>
val selectJson =SQL("select name from test").on().apply().collect {
case Row(id:Long, Some(name:String)) =>
new TestContent(name)
}
//.head
//JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
}
}
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
Ok("Done")
//Ok(response)
}
Output is: Done(application is executing fine without any exceptions, of course json data is not coming since I am returning: Done only, so getting output: "Done")
case b. If I do like this: getting error: not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n. I really not sure how can I get my response for it ?
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
// Ok("Done")
Ok(response)
}
model:
def getTestValuesFromTable(): JsValue = {
DB.withConnection { implicit connection =>
val selectJson = SQL("select * from test")
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
// val selectJson =SQL("select name from test").on().apply().collect {
// case Row(id:Long, Some(name:String)) =>
// new TestContent(name)
// }
//.head
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)//not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n.
}
}
Please let me know how to get my response ?
getJsonValuesFromTable method return nothing (Unit). To fix it change definition of this method to
def getJsonValuesFromTable(testContent: TestContent) = {
or explicitly setting type:
def getJsonValuesFromTable(testContent: TestContent): Unit = {
Also as a next step to let client know that you are returning json, you should set content type:
Ok(Json.obj(response)).as("application/json")

json4s object extraction with extra data

I'm using spray with json4s, and I've got the implementation below to handle put requests for updating objects... My problem with it, is that I first extract an instance of SomeObject from the json, but being a RESTful api, I want the ID to be specified in the URL. So then I must somehow create another instance of SomeObject that is indexed with the ID... to do this, I'm using a constructor like SomeObject(id: Long, obj: SomeObject). It works well enough, but the implementation is ugly and it feels inefficient. What can I do so I can somehow stick the ID in there so that I'm only creating one instance of SomeObject?
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = SomeObject(id, extractedObject)
handleRequest(extractedObjectWithId)
}
}
}
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime) {
def this(data: String, someValue: Double, someDate: DateTime) = this(0, data, someValue, someDate)
def this(id: Long, obj: SomeObject) = this(id, obj.data, obj.someValue, obj.someDate)
}
I figured out a solution to this after digging around through the docs for awhile:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.merge {
("id", id)
}.extract[SomeObject]
handleRequest(extractedObject)
}
}
}
}
Since id field is not set on all instances, it means it is optional so use Option type to indicate it. Define your case class with id: Option[Long] field. This make json parser to skip id field when it is absent but allow you to assign a value when you have.
case class SomeObject(id: Option[Long], data: String, someValue: Double, someDate: DateTime)
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = extractedObject.copy(id = Some(id))
handleRequest(extractedObjectWithId)
}
}
}
}
And don't worry about performance impacts of creating new objects. It may affect performance much less than you thought. You should measure performance before improving it.
I do not know about efficiency, but you can make your code "less ugly" by defining a SomeObjectBuilder, to which you extract your JSON value.
case class SomeObjectBuilder(data: String, someValue: Double, someDate: DateTime) {
def setId(id: Long) = SomeObject(id, data, someValue, someDate)
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime)
With the extraction:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObjectBuilder]
val extractedObjectWithId = extractedObject.setId(id)
handleRequest(extractedObjectWithId)
}
}
}
}
This way, you are not using a default id set to zero, which is, if I understand correctly, never correct. The only reason you're setting it to zero is that the value is not known by the extractor, so, using the builder, you make a partial instantiation explicit.

Render json data to the view with play! scala 2.2.3

Here is a beginner question :
I have defined Event like this :
case class Event(id: Pk[Long], name: String, userId: Pk[Long])
object Event {
private val EventParser: RowParser[Event] = {
get[Pk[Long]]("id") ~
get[String]("name") ~
get[Pk[Long]]("userId") map {
case id ~ name ~ userId => Event(id, name, userId)
}
}
def findAll(): Seq[Event] = {
DB.withConnection { implicit connection =>
SQL("select * from events").as(EventParser *)
}
}
}
And I render it to the view like this :
def events = Action {
val events: Seq[Event] = Event.findAll()
Ok(views.html.events(events))
}
But I would like to return Json data.
Json.toJson(events) can't be used since events type is Seq.
I didn't find a good tutorial on the subject and I tried to follow this answer : play framework working with json objects in Scala but it doesn't seem to work with play 2.2.
So my question is : Do you know an easy way to render a sequence in Json to the view after accessing database?
Try this:
import play.api.libs.json._
object Event {
...
implicit def pkWrites[T : Writes]: Writes[Pk[T]] = Writes {
case anorm.Id(t) => implicitly[Writes[T]].writes(t)
case anorm.NotAssigned => JsNull
}
implicit val eventWrites = Json.writes[Event]
}

handling json requests and responses in scala play framework and send to angular js java script

I am working on implementation of library system using play framework and angularjs.
suppose to search for a book in the library the user enters the keyword value in the input field, this value is received by the controller from the GET request. I need to search the MySQL database for the list of the books, convert them to json request and display them back in the search page which is implemented using angularjs.
I don't understand how to use json and send the result back to the web page.
GET /books/all/search/:by/:value controllers.Books.listBooks(by: String, value: String)
case class Book (
bookId: String,
title: String,
author: String,
category:String,
price: Int,
location: String,
status: String
)
object Book{
val bookParse = {
get[String]("book.bookId") ~
get[String]("book.title") ~
get[String]("book.author") ~
get[String]("book.category") ~
get[Int]("book.price") ~
get[String]("book.location") ~
get[String]("book.status")map {
case bookId~title~author~category~price~location~status => Book(bookId,title, author, category, price, location, status)
}
}
def searchByBookId(bookId: String) : List[Book]= {
DB.withConnection {implicit connection =>
SQL("select * from book where bookId = {bookId}").as(Book.bookParse *)
}
}
object Books extends Controller {
def listBooks(by: String, value:String): List[Book] =
{
if (by == "byBookId" ) Book.searchByBookId(value)
else if(by == "byTitle")Book.searchByTitle(value)
else Book.searchByAuthor(value)
}
}
Now i need to send the List[Book] result to the web page
import play.api.libs.json._
implicit val bookFormat = Json.format[Book]
def listBooks(by: String, value: String) = Action {
val books = if (by == "byBookId" ) Book.searchByBookId(value)
else if(by == "byTitle")Book.searchByTitle(value)
else Book.searchByAuthor(value)
Ok(Json.toJson(books))
}
The implicit val bookFormat needs to be either on the Book companion object, or in scope when Json.toJson is called.
More documentation on JSON:
http://www.playframework.com/documentation/2.2.x/ScalaJson