ReactiveMongoDB plugin use id in JSONCollections - mongodb

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.

Related

No instance of play.api.libs.json.Format is available for akka.actor.typed.ActorRef[org.knoldus.eventSourcing.UserState.Confirmation]

No instance of play.api.libs.json.Format is available for akka.actor.typed.ActorRef[org.knoldus.eventSourcing.UserState.Confirmation] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
[error] implicit val userCommand: Format[AddUserCommand] = Json.format
I am getting this error even though I have made Implicit instance of Json Format for AddUserCommand.
Here is my code:
trait UserCommand extends CommandSerializable
object AddUserCommand{
implicit val format: Format[AddUserCommand] = Json.format[AddUserCommand]
}
final case class AddUserCommand(user:User, reply: ActorRef[Confirmation]) extends UserCommand
Can anyone please help me with this error and how to solve it?
As Gael noted, you need to provide a Format for ActorRef[Confirmation]. The complication around this is that the natural serialization, using the ActorRefResolver requires that an ExtendedActorSystem be present, which means that the usual approaches to defining a Format in a companion object won't quite work.
Note that because of the way Lagom does dependency injection, this approach doesn't really work in Lagom: commands in Lagom basically can't use Play JSON.
import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps
import play.api.libs.json._
class PlayJsonActorRefFormat(system: ExtendedActorSystem) {
def reads[A] = new Reads[ActorRef[A]] {
def reads(jsv: JsValue): JsResult[ActorRef[A]] =
jsv match {
case JsString(s) => JsSuccess(ActorRefResolver(system.toTyped).resolveActorRef(s))
case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError(Seq("ActorRefs are strings"))))) // hopefully parenthesized that right...
}
}
def writes[A] = new Writes[ActorRef[A]] {
def writes(a: ActorRef[A]): JsValue = JsString(ActorRefResolver(system.toTyped).toSerializationFormat(a))
}
def format[A] = Format[ActorRef[A]](reads, writes)
}
You can then define a format for AddUserCommand as
object AddUserCommand {
def format(arf: PlayJsonActorRefFormat): Format[AddUserCommand] = {
implicit def arfmt[A]: Format[ActorRef[A]] = arf.format
Json.format[AddUserCommand]
}
}
Since you're presumably using JSON to serialize the messages sent around a cluster (otherwise, the ActorRef shouldn't be leaking out like this), you would then construct an instance of the format in your Akka Serializer implementation.
(NB: I've only done this with Circe, not Play JSON, but the basic approach is common)
The error says that it cannot construct a Format for AddUserCommand because there's no Format for ActorRef[Confirmation].
When using Json.format[X], all the members of the case class X must have a Format defined.
In your case, you probably don't want to define a formatter for this case class (serializing an ActorRef doesn't make much sense) but rather build another case class with data only.
Edit: See Levi's answer on how to provide a formatter for ActorRef if you really want to send out there the actor reference.

Transforming Scala case class into JSON

I have two case classes. The main one, Request, contains two maps.
The first map has a string for both key and value.
The second map has a string key, and value which is an instance of the second case class, KVMapList.
case class Request (var parameters:MutableMap[String, String] = MutableMap[String, String](), var deps:MutableMap[String, KVMapList] = MutableMap[String, KVMapList]())
case class KVMapList(kvMap:MutableMap[String, String], list:ListBuffer[MutableMap[String, String]])
The requirement is to transform Request into a JSON representation.
The following code is trying to do this:
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility
import com.fasterxml.jackson.databind.ObjectMapper
def test(req:Request):String {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY)
var jsonInString: String = null
try {
jsonInString = mapper.writeValueAsString(request)
}
catch {
=case e: IOException => {
e.printStackTrace
}
jsonString
}
This however is not working. Even when the Request class is populated, the output is :
{"parameters":{"underlying":{"some-value":""},"empty":false,"traversableAgain":true},"deps":{"sizeMapDefined":false,"empty":false,"traversableAgain":true}}
Using the JSON object mapper with corresponding Java classes is straightforward, but have not yet got it working in Scala. Any assistance is very much appreciated.
Jackson is more of a bad old memory in Scala to some degree. You should use a native Scala library for JSON processing, particularly one really good at compile time derivation of JSON serializers, such as circe.
I'm aware this doesn't directly answer your question, but after using circe I would never go back to anything else.
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
val req = new Request(...)
val json = req.asJson.noSpaces
val reparsed = decode[Request](json)
On a different note, using mutable maps inside case classes is as non-idiomatic as it gets, and it should be quite trivial to implement immutable ops for your maps using the auto-generated copy method.
case class Request(parameters: Map[String, String] {
def +(key: String, value: String): Request = {
this.copy(parameters = parameters + (key -> value))
}
}
You should really avoid mutability wherever possible, and it looks like avoiding it here wouldn't be much work at all.
I am not sure what this ScalaObjectMapper does, doesn't look like it is useful.
If you add mapper.registerModule(DefaultScalaModule) in the beginning, it should work ... assuming that by MutableMap you mean mutable.Map, and not some sort of home-made class (because, if you do, you'd have to provide a serializer for it yourself).
(DefaultScalaModule is in jackson-module-scala library. Just add it to your build if you don't already have it).

What would be the best way in scala to convert between case classes?

I have a model class im getting back from an api inside my servcie, and when I return it to some client I want to use my own model to keep it simpler and cleaner for the client.
example :
case class ReturnedModel(succeed: Option[String], reason: Reason, transactionId: List[Int], opId: Option[Int])
case class MyReturnedModel(reason: String)
I might need to do more of those in the future so I thought maybe there is a best practice to do it that I dont know of, thanks!
You can use a companion object with a custom "apply" method:
case class MyReturnedModel(reason: String)
object MyReturnedModel {
def apply(mod: ReturnedModel) = MyReturnedModel(mod.reason.toString)
}
val data: ReturnedModel = ... // Some instance of ReturnedModel
val mr = MyReturnModel(data)
Just note that the case class and its companion object need to be in the same file for this to work.
Depending on your use case:
sealed trait IKnowAReason { def reason:String }
case class ReturnedModel(succeed: Option[String], reason: Reason,
transactionId: List[Int], opId: Option[Int]) extends IKnowAReason
Now replace uses of MyReturnedModel with IKnowAReason. Notice the sealed, it will ensure that there are no other implementations of IKnowAReason outside the same source file.
If you have access to change the ReturnedModel, you could use traits like #pedrofurla has demonstrated.
If you are unable to modify the ReturnedModel, you could declare an implicit function to convert all instance of ReturnedModel to MyReturnedModel like this:
implicit def returnedModelToMyModel(returnedModel: ReturnedModel): MyReturnedModel = {
// Have some logic to convert their model to your model
MyReturnedModel(returnedModel.reason.toString)
}
Then whenever you get a ReturnedModel from the API, you can use it anywhere you are expecting an instance of MyReturnedModel:
def doWork(myReturnedModel: MyReturnedModel) = { /* Logic that needs and instance of MyReturnedModel */ }
// Grab an instance of ReturnModel from the API
val returned: ReturnedModel = ???
// Will get converted when you need it to be an instance of MyReturnedModel
doWork(returned)
The compiler will try to preform implicit conversions when it finds that the type you have passed is not correct, at which point it will look for an implicit conversion to satisfy the type conversion.

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