elastic4s: deserializing search results - scala

I'm using elastic4s library to query elasticsearch (ES). Version of elastic4s and ES itself 2.4.0.
Suppose I have a compound object that I put to ES like
case class MyObject(id: Long, vall: KeyVal, vals: Seq[KeyVal])
where KeyVal is
case class KeyVal(id: Long, name: String)
Now I queried ES and got the response which I want to deserialiize back to MyObject:
implicit object MyObjectHitAs extends HitAs[MyObject] {
override def as(hit: RichSearchHit): MyObject = {
MyObject(
hit.field("id").getValue[String]
KeyVal(hit.field("vall.id").getValue[Long], field("vall.name").getValue[String]),
//what should I code here to get the Seq[KeyVal] ???
)
}
}
Please explain how can I deserialize the Array of KeyVal. Thank you.

In the more recent versions of elastic4s, ie 5.0 onwards, you would use the HitReader typeclass. Your example would then look like this.
implicit object MyObjectHitAs extends HitReader[MyObject] {
override def read(hit: Hit): Either[Throwable, MyObject] = {
val obj = MyObject(
hit.sourceField("id").toString.toLong,
KeyVal(hit.sourceField("vall.id").toString.toLong, hit.sourceField("vall.name").toString),
hit.sourceField("vals").asInstanceOf[Seq[AnyRef]].map { entry =>
KeyVal(hit.sourceField("vall.id").toString.toLong, hit.sourceField("vall.name").toString)
}
)
Right(obj)
}
}
Although it is a lot easier to use the built in json mappers than hand craft it.

Related

ReactiveMongoDB plugin use id in JSONCollections

I described model as case class WorkAreaType(name: String) and findAll method
object WorkAreaType {
import system.db.Mongo.JsonFormats._
def findAll = Await.result(Mongo.workAreaTypes.find(Json.obj()).cursor[WorkAreaType].collect[List](), 3 seconds)
}
JsonFormats:
object JsonFormats {
implicit val workAreaTypeFormat = Json.format[WorkAreaType]
}
Mongo.workAreaTypes is a JSONCollection val workAreaTypes: JSONCollection = db.collection[JSONCollection]("WorkAreaTypes")
How can I read mongo _id object after call WorkAreaType.findAll in list?
The quick but not so great way is to define your case class as
case class WorkAreaType(_id:Option[BSONObjectID] , name: String)
The reason this is not so great is that this will make reactive mongo "leak" through your repository/dao/storage layer whatever you name it.
A "better" option would be to manipulate the json to "flatten"the object id before you read it to a scala object and inflate it after you write it back to json. This can be accomplished using Json transformers or JsZippers but takes a bit of research.

Create instance of object with only string of class name.

I'm new to scala and trying to figure things out as I go. I'm working with the Play 2.x framework and using Scala to build my app. I have a route defined
GET /:tableName controllers.Application.getTable(tableName)
In the controller I would like to take the name of the table and use it as I would the class. For example in the db if have a table named People. I would like it to map to the Slick model for People that has the function getAll. I looked into typeof[t], but couldn't get it to work. below it is an example of what I would like to do.
def getTable(tableName: String) = Action {
Ok(Json.toJson(typeOf[tableName].getAll))
}
You're going to need a bit more than that, to get this accomplished ;) First of all, Slick requires a DB session, so that needs to be handled somewhere. Meaning a Slick Table getAll won't work by itself.
I would do something like this (sorry, typing this up without an IDE, so it may not compile):
case class Person(...)
object People extends Table[Person](PeopleDAO.table) {
def * = ...
}
trait DAO[T] {
val table: String
def getAll: Seq[T]
}
object PeopleDAO extends DAO[Person] {
override val table = "people"
def getAll = {
DB withSession { implicit session =>
Query(People).list
}
}
}
object Controller {
def getTable(tableName: String) = Action {
val dao: DAO[_] = tableName.toLowerCase match {
case PeopleDAO.table => PeopleDAO
case _ => throw new IllegalArgumentException("Not a valid table.")
}
Ok(Json.toJson(dao.getAll))
}
}

Json Reads in Play Framework

I've been trying to use the Reads[A] in Play as discussed in this post.
Handling JSON requests in Play Framework 2.0 Scala
However, when I tried to do something similar, I ended up getting this error.
object creation impossible, since method reads in trait Reads of type (json: play.api.libs.json.JsValue)models.SomeObject.AnotherObject is not defined
I currently have:
package models
object SomeObject {
case class AnotherObject(val name: String)
implicit object AnotherObjectReads extends Reads[AnotherObject] {
def read(json: JsValue) =
AnotherObject((json \ "name").as[String])
}
}
And I'm using it in the following way:
def callFunc = Action(BodyParsers.parse.json) { request =>
request.body.asOpt[SomeObject.AnotherObject].map {
//Logic
}.getOrElse(BadRequest)
}
Am I doing something wrong in my code?
I think you have it misspelled. The method is called reads not read
http://www.playframework.org/documentation/api/2.0.4/scala/index.html#play.api.libs.json.Reads

How to convert a scala object with list to a MongoDBObject via Casbah

I'm learning MongoDB and Casbah by writing a simple app. Got stuck when I try to convert an object with a list member into a MongoDB Object. Here is my class
case class BorrowerRecord( name: String, checkedOut: List[BookTag]) {
require(!name.isEmpty)
require(!checkedOut.isEmpty)
}
case class BookTag (subject: Subject, bookName: String) {
require(!bookName.isEmpty)
}
case class Subject (name: String, category: Category) {
require(!name.isEmpty)
}
Category is a sealed trait with 2 case class implementation, I intended to use this like "Enum"
sealed trait Category {
def name: String
}
object Category {
case object Computing extends Category { val name = "Computing"}
case object Math extends Category { val name = "Math"}
}
So, a instance of BorrowerRecord will keep what books a person checked out from the library, each book is identified by a BookTag object. A BookTag keeps some information about a book like bookname, subject name, Category, etc.
Lets say I've a BorrowerRecord and want to save it to MongoDB
val borrowOnToday = BorrowerRecord( "My Name", List( BookTag(Subject("J2EE", Category.Computing), "Head First Java"),
BookTag(Subject("Linear Algebra", Category.Math), "Algebra for Dummies")))
How should I convert this to MongoDBObject using Casbah ?
Or Casbah is not the way to go and there're other libraries that can help me persist this into MongoDB more easily?
To work with case classes use the salat (press <- and -> to move through the presentation).
It is pretty simple:
case class Alpha(x: String)
scala> val a = Alpha(x = "Hello world")
a: com.novus.salat.test.model.Alpha = Alpha(Hello world)
scala> val dbo = grater[Alpha].asDBObject(a)
dbo: com.mongodb.casbah.Imports.DBObject = { "_typeHint" :
"com.novus.salat.test.model.Alpha" , "x" : "Hello world"}
scala> val a_* = grater[Alpha].asObject(dbo)
a_*: com.novus.salat.test.model.Alpha = Alpha(Hello world)
Usually, I'm using them both: casbah to querying to/from Mongo, and salat to make a conversions to case classes and vice versa.
And yes, salat supports case classes with Lists (here is the list of supported collections).
I use my own library Subset (I've open-sourced it recently) along with MongoDB Java driver. Unlike Salat it's explicit, you have to declare all the serialization code, though Subset helps keep it quite simple. You'll get ability to create queries as a bonus.
For your data model, the code may look like
object BorrowerRecord {
val name = "name".fieldOf[String]
val checkedOut = "cout".fieldOf[List[BookTag]]
def toDBO(rec: BorrowerRecord): DBObject =
name(rec.name) ~ checkedOut(rec.checkedOut)
}
Subset knows how to serialize List[T], but it needs an implicit ValueWriter[BookTag] for that:
object BookTag {
val subject = "subj".fieldOf[Subject]
val name = "name".fieldOf[String]
implicit def writer = ValueWriter[BookTag](bt =>
(subject(bt.subject) ~ name(bt.name)).get
)
}
I hope you got the idea to continue with Subject and Category

Define custom serialization with Casbah / Salat - or delegate serialization to member?

I'm in the process of learning Scala for a new project having come from Rails. I've defined a type that is going to be used in a number of my models which can basically be thought of as collection of 'attributes'. It's basically just a wrapper for a hashmap that delegates most of its responsibilities to it:
case class Description(attributes: Map[String, String]) {
override def hashCode: Int = attributes.hashCode
override def equals(other: Any) = other match {
case that: Description => this.attributes == that.attributes
case _ => false
}
}
So I would then define a model class with a Description, something like:
case class Person(val name: String, val description: Description)
However, when I persist a Person with a SalatDAO I end up with a document that looks like this:
{
name : "Russell",
description:
{
attributes:
{
hair: "brown",
favourite_color: "blue"
}
}
}
When in actual fact I don't need the nesting of the attributes tag in the description tag - what I actually want is this:
{
name : "Russell",
description:
{
hair: "brown",
favourite_color: "blue"
}
}
I haven't tried, but I reckon I could get that to work if I made Description extend a Map rather than contain one, but I'd rather not, because a Description isn't a type of Map, it's something which has some of the behaviour of a Map as well as other behaviour of its own I'm going to add later. Composition over inheritance and so on.
So my question is, how can I tell Salat (or Casbah, I'm actually a bit unclear as to which is doing the conversion as I've only just started using them) how to serialize and deserialize the Description class? In the casbah tutorial here it says:
It is also possible to create your own custom type serializers and
deserializers. See Custom Serializers and Deserializers.
But this page doesn't seem to exist. Or am I going about it the wrong way? Is there actually a really simple way to indicate this is what I want to happen, an annotation or something? Or can I simply delegate the serialization to the attributes map in some way?
EDIT: After having a look at the source for the JodaTime conversion helper I've tried the following but have had no luck getting it to work yet:
import org.bson.{ BSON, Transformer }
import com.mongodb.casbah.commons.conversions.MongoConversionHelper
object RegisterCustomConversionHelpers extends Serializers
with Deserializers {
def apply() = {
super.register()
}
}
trait Serializers extends MongoConversionHelper
with DescriptionSerializer {
override def register() = {
super.register()
}
override def unregister() = {
super.unregister()
}
}
trait Deserializers extends MongoConversionHelper {
override def register() = {
super.register()
}
override def unregister() = {
super.unregister()
}
}
trait DescriptionSerializer extends MongoConversionHelper {
private val transformer = new Transformer {
def transform(o: AnyRef): AnyRef = o match {
case d: Description => d.attributes.asInstanceOf[AnyRef]
case _ => o
}
}
override def register() = {
BSON.addEncodingHook(classOf[Description], transformer)
super.register()
}
}
When I call RegisterCustomConversionHelpers() then save a Person I don't get any errors, it just has no effect, saving the document the same way as ever. This also seems like quite a lot to have to do for what I want.
Salat maintainer here.
I don't understand the value of Description as a wrapper here. It wraps a map of attributes, overrides the default equals and hashcode impl of a case class - which seems unnecessary since the impl is delegated to the map anyhow and that is exactly what the case class does anyway - and introduces an additional layer of indirection to the serialized object.
Have you considered just:
case class Person(val name: String, val description: Map[String, String])
This will do exactly what you want out of box.
In another situation I would recommend a simple type alias but unfortunately Salat can't support type aliases right now due to some issues with how they are depicted in pickled Scala signatures.
(You probably omitted this from your example from brevity, but it is best practice for your Mongo model to have an _id field - if you don't, the Mongo Java driver will supply one for you)
There is a working example of a custom BSON hook in the salat-core test package (it handles java.net.URL). It could be that your hook is not working simply because you are not registering it in the right place? But still, I would recommend getting rid of Description unless it is adding some value that is not evident from your example above.
Based on #prasinous' answer I decided this wasn't going to be that easy so I've changed my design a bit to the following, which pretty much gets me what I want. Rather than persisting the Description as a field I persist a vanilla map then mix in a Described trait to the model classes I want to have a description, which automatically converts the map to Description when the object is created. Would appreciate it if anyone can point out any obvious problems to this approach or any suggestions for improvement.
class Description(val attributes: Map[String, String]){
//rest of class omitted
}
trait Described {
val attributes: Map[String, String]
val description = new Description(attributes)
}
case class Person(name: String, attributes: Map[String, String]) extends Described