I cannot retrieve BSONDocument correctly - scala

I have been spending time 3 hours and I wasn't success trying to retrieve a BSONDocument with all of the attributes. I don't why I only get the first attribute... where are the others?
I wrote the following test
describe("Testing reactive map user") {
it(" convert an user to BSONDocument") {
val basicProfile = BasicProfile("1", "1", Option("pesk"), Option("pesk"),
Option("pesk pesk"), Option("pesk#gmail.com"), Option("url"),
AuthenticationMethod.UserPassword, Option(new OAuth1Info("token", "secret")),
Option(new OAuth2Info("token", Option("secret"))),
Option(new PasswordInfo("hasher", "password", Option("salt"))))
val user = User(Option(BSONObjectID.generate), basicProfile, List(), "31.55, 53.66", List[User](), Option(DateTime.now))
val result = User.UserBSONWriter.write(user)
assert(result.getAs[String]("providerId") == "1")
}
}
The UserBSONWriter
implicit object UserBSONWriter extends BSONDocumentWriter[User] {
def write(user: User): BSONDocument = {
val doc = BSONDocument(
"_id" -> user.id.getOrElse(BSONObjectID.generate),
"providerId" -> BSONString(user.basicProfile.providerId),
"userId" -> BSONString(user.basicProfile.userId))
println(doc)
doc
}
}
And I attach the screenshot of the console. I'm trying to get the value providerId, which is next to BSONObjectID, but I can just only get the first attribute.
I will appreciate a lot if somebody can help me. And I have other comment, Im getting some headaches because of the implicitly system which is used by scala's BSON API. It is not trivial to find some docs about these all implicit conversions.
Outupt of println(doc)
BSONDocument(non-empty)

Related

MongoDB Codec in Scala broken?

I have an app written in Scala to access a Mongo database in which I can query and insert data perfectly fine.
However when I try to use collection.distinct("user") I get the error:
Unknown Error org.bson.codecs.configuration.CodecConfigurationException:
Can't find a codec for class scala.runtime.Nothing$.
After doing some googling I found that I would need to specify a codec, so I initalised the MongoClient with one:
val clusterSettings = ClusterSettings
.builder()
.hosts(scala.collection.immutable.List(new com.mongodb.ServerAddress(addr)).asJava)
.build()
val settings = MongoClientSettings.builder()
.codecRegistry(MongoClient.DEFAULT_CODEC_REGISTRY)
.clusterSettings(clusterSettings)
.build()
val mongoClient: MongoClient = MongoClient(settings)
Still no luck, got the same error.
I figured I would need to make a custom codec as per this web page:
class NothingCodec extends Codec[Nothing] {
override def encode(writer:BsonWriter, value:Nothing, encoderContext:EncoderContext)={}
override def decode(reader: BsonReader, decoderContext: DecoderContext): Nothing = {
throw new Exception
}
override def getEncoderClass(): Class[Nothing] = {
classOf[Nothing]
}
}
But this doesn't work nor doesn't make sense, Nothing is not a valid return type; there exist no instances of this type.
As a result this obvisouly does not work with a new error of Unknown Error java.lang.Exception(Obviously!)
Am I missing something with the Mongo Scala Driver? Or is it just broken?
Use Document instead:
collection.distinct[Document]("user")
// i.e.
case class User(name: String, id: String)
records.distinct[Document]("user")
.toFuture()
.map(_ map { doc => getUserFromBson(doc).get })
def getUserFromBson(doc: Document): Option[User] = {
for {
name <- doc.get("name") map { x => x.asString().getValue }
id <- doc.get("id") map { x => x.asString().getValue }
}yield(User(name, id))
}
hope this helps.

Make CRUD operations with ReactiveMongo

I have started to learn scala recently and trying to create simple api using akka HTTP and reactivemongo.
Have problems with simple operations. Spend a lot of time digging docks, official tutorials, stackoverflow etc. Probably I am missing something very simple.
My code:
object MongoDB {
val config = ConfigFactory.load()
val database = config.getString("mongodb.database")
val servers = config.getStringList("mongodb.servers").asScala
val credentials = Lis(Authenticate(database,config.getString("mongodb.userName"), config.getString("mongodb.password")))
val driver = new MongoDriver
val connection = driver.connection(servers, authentications = credentials)
//val db = connection.database(database)
}
Now I would like to make basic CRUD operations. I am trying different code snippets but can't get it working.
Here are some examples:
object TweetManager {
import MongoDB._
//taken from docs
val collection = connection.database("test").
map(_.collection("tweets"))
val document1 = BSONDocument(
"author" -> "Tester",
"body" -> "test"
)
//taken from reactivemongo tutorial, it had extra parameter as BSONCollection, but can't get find the way of getting it
def insertDoc1(doc: BSONDocument): Future[Unit] = {
//another try of getting the collection
//def collection = for ( db1 <- db) yield db1.collection[BSONCollection]("tweets")
val writeRes: Future[WriteResult] = collection.insert(doc)
writeRes.onComplete { // Dummy callbacks
case Failure(e) => e.printStackTrace()
case Success(writeResult) =>
println(s"successfully inserted document with result: $writeResult")
}
writeRes.map(_ => {}) // in this example, do nothing with the success
}
}
insertDoc1(document1)
I can't do any operation on the collection. IDE gives me: "cannot resolve symbol". Compiler gives error:
value insert is not a member of scala.concurrent.Future[reactivemongo.api.collections.bson.BSONCollection]
What is the correct way of doing it?
You are trying to call the insert operation on a Future[Collection], rather than on the underlying collection (calling operation on Future[T] rather than on T is not specific to ReactiveMongo).
It's recommanded to have a look at the documentation.

Insert document with ReactiveMongo does not use BSONWriter or Reader

I have the following class:
case class DeviceRegistration(deviceData: DeviceData,
pin: String,
created: DateTime) {}
Type DeviceData is defined simply as 4 string fields.
I've been trying unsuccessfully to insert a type of DeviceRegistration to a mongo collection. I want to make sure the date is stored as a ISODate and not a NumberLong, so I implemented custom readers and writers.
implicit object DeviceDataWriter extends BSONDocumentWriter[DeviceData] {
def write(data: DeviceData): BSONDocument = BSONDocument(
DEVICE_ID_KEY -> data.deviceId,
MODEL_KEY -> data.model,
BRAND_KEY -> data.brand,
MANUFACTURER_KEY -> data.manufacturer)
}
implicit object DeviceRegistrationWriter extends BSONDocumentWriter[DeviceRegistration] {
def write(registration: DeviceRegistration): BSONDocument = BSONDocument(
DEVICE_DATA_KEY -> registration.deviceData,
PIN_KEY -> registration.pin,
CREATED_KEY -> BSONDateTime(registration.created.getMillis))
}
implicit object DeviceDataReader extends BSONDocumentReader[DeviceData] {
def read(doc: BSONDocument): DeviceData = {
val deviceId = doc.getAs[String](DEVICE_ID_KEY).get
val model = doc.getAs[String](MODEL_KEY)
val brand = doc.getAs[String](BRAND_KEY)
val manufacturer = doc.getAs[String](MANUFACTURER_KEY)
DeviceData(deviceId, model, brand, manufacturer)
}
}
implicit object DeviceRegistrationReader extends BSONDocumentReader[DeviceRegistration] {
def read(doc: BSONDocument): DeviceRegistration = {
val deviceData = doc.getAs[DeviceData](DEVICE_DATA_KEY).get
val pin = doc.getAs[String](PIN_KEY).get
val created = doc.getAs[BSONDateTime](CREATED_KEY).map(dt => new DateTime(dt.value))
DeviceRegistration(deviceData, pin, created.get)
}
}
I'm trying to insert the document with the following code:
def save(deviceRegistration: DeviceRegistration): Future[DeviceRegistration] = {
deviceRegistrations.insert(deviceRegistration).map(result => deviceRegistration)
}
To retrieve, I'm doing this:
def findDeviceRegistrationRequest(deviceConfirmationData: DeviceConfirmationData) = {
deviceRegistrations.find(BSONDocument("pin" -> deviceConfirmationData.pin))
.one[DeviceRegistration](ReadPreference.Primary)
}
The record is stored as this:
{ "_id" : ObjectId("56dea8d0d8cadd6ff70690d8"), "deviceData" : { "deviceId" : "kdsajkldjsalkda" }, "pin" : "9914", "created" : NumberLong("1457432783921") }
The created date is clearly not being serialized by my writer. It seems reactivemongo is using some default writer.
Likewise, when I read, I get an exception:
Caused by: java.lang.RuntimeException: (/created,List(ValidationError(List(error.expected.date),WrappedArray())))
So it's clearly also not using my reader.
I didn't have any luck googling around. What am I missing?
I also tried to find a way to specifically set the writer and reader I want to use (instead of relying on the implicit mechanism), but I was not able to figure it out.
Any pointers in the right direction would be most appreciated.

Isn't there any other way to convert bson objects to case class and the vice-versa rather than the implicit conversion in scala?

I have went through some of the reactive mongo example and find the conversion of the model and the bson object as below.
case class Artist(
name: String,
slug: String,
artistId: Int,
albums: Vector[Album] = Vector(),
id: ObjectId = new ObjectId())
object ArtistMap {
def toBson(artist: Artist): DBObject = {
MongoDBObject(
"name" -> artist.name,
"slug" -> artist.slug,
"artistId" -> artist.artistId,
"albums" -> artist.albums.map(AlbumMap.toBson),
"_id" -> artist.id
)
}
def fromBson(o: DBObject): Artist = {
Artist(
name = o.as[String]("name"),
slug = o.as[String]("slug"),
artistId = o.as[Int]("artistId"),
albums = o.as[MongoDBList]("albums").toVector
.map(doc => AlbumMap.fromBson(doc.asInstanceOf[DBObject])),
id = o.as[ObjectId]("_id")
)
}
}
Is there any other way to get rid of this overhead of mapping each field of the case classes, maybe some framework over reactivemongo or any utility for this?
I don't understand your comment too, but my assumption you want functionality like that.
(I haven't written on scala for a few month, so sorry for any stupid mistake.)
case class Album(name: String, year: Int)
/*
then you may implement serialization interface(trait) or use macros
*/
implicit val albumHandler = Macros.handler[Album]
/*
I'm not sure is there build in implementation for the Vector[].
*/
object VectorHandler[T](implicit handler: BSONHandler[BSONValue, T]) extends BSONHandler[BSONArray, Vector[T]]{
def read(bson: BSONArray): Vector[T]{
/*
iterate over array and serialize it in a Vector with a help of the handler,
or just persist Vector[] as List[]
*/
}
def write(t: Vector[T]): BSONArray {
}
}
implicit val albumVectorHandler = VectorHandler[Album]()
implicit val artistHandler = Macros.handler[Atrist]

Play! Form that selects an item from a separate mongo collection

So for a system I am developing I am trying to do something similar to this:
If I have a Model called User, that has an _id (ObjectId), username, password, and then I am trying to create a new appointment, my form would look for a patient (display the patient name in the dropdown but really would pick up the patient's ObjectId), and appointment time.
Now I've looked everywhere and can't find anything remotely close to the solution i'm trying to attain.
In Application.scala, I have:
val appointmentForm= Form(
tuple(
"patient" -> nonEmptyText, // ObjectId
"startTime" -> nonEmptyText))
I am not sure how to quite work my view in order to reflect the patient. I know you have to do something like this:
#select(appointmentForm("patient"), options(..)
Can anyone give me any ideas as to how I can look up the patients for this example to pick up a Mongo ObjectId.
The ORM I am using btw is https://github.com/leon/play-salat
here is an example how I would do it:
routes:
GET /test controllers.Test.show
POST /test controllers.Test.submit
view:
#(f: Form[(ObjectId, String)], users: Seq[(ObjectId, String)])
#import helper._
#form(action = routes.Test.submit) {
#select(f("patient"), options = users.map(user => (user._1.toString, user._2)))
#inputText(f("startTime"))
<input type="submit" value="Submit!">
}
controller:
package controllers
import org.bson.types.ObjectId
import play.api.data.format.Formatter
import play.api.mvc._
import play.api.data.Forms._
import play.api.data._
import play.api.data.FormError
import play.api.Logger
object Test extends Controller {
/**
* Converts an ObjectId to a String and vice versa
*/
implicit object ObjectIdFormatter extends Formatter[ObjectId] {
def bind(key: String, data: Map[String, String]) = {
val error = FormError(key, "error.required.ObjectId", Nil)
val s = Seq(error)
val k = data.get(key)
k.toRight(s).right.flatMap {
case str: String if (str.length() > 0) => Right(new ObjectId(str))
case _ => Left(s)
}
}
def unbind(key: String, value: ObjectId) = Map(key -> value.toStringMongod())
val objectId: Mapping[ObjectId] = of[ObjectId]
}
// import to get objectId into scope
import ObjectIdFormatter._
// define user tuples consisting of username and ObjectId for the dropdown. In real lif the list is probably fetched from the db
def users: Seq[(ObjectId, String)] =
Seq((new ObjectId("4f456bf744aed129d04db1bd"), "dieter"), (new ObjectId("4faa410b44aec5a0a980599f"), "eva"))
val appointmentForm= Form(
tuple(
"patient" -> objectId, // use the ObjectIdFormatter
"startTime" -> nonEmptyText))
def show = Action {
Ok(views.html.test(appointmentForm, users))
}
def submit = Action { implicit request =>
appointmentForm.bindFromRequest.fold(
formWithErrors => {
Logger.warn("errors: " + formWithErrors.errors)
BadRequest(views.html.test(formWithErrors, users))
},
formContent => {
Logger.info("formContent: " + formContent)
Ok(views.html.test(appointmentForm, users))
})
}
}
Fyi, I was able to finally solve the problem after seeing this wonderful comment by maxmc. It turns out my problem was really a fundamental scala issue. I did not realize that List is an implementation of Seq. so using Mongo, in this example, what you have to do is in your code is the following:
CONTROLLER
def newAppointment= Action {
val pList = Patient.findAll.toList
Ok(views.html.admin.newuser(appointmentForm, pList))
}
View:
#(appointmentForm: Form[(String, String, String)], pList : List[Patient])
...
...
...
#select(
appointmentForm("patient"),
pList.map{ p =>
p.id.toString -> (p.patientName)
},
'_default -> "--- Select a Patient ---",
'_label -> "Patient"
)
the Model.findAll function returns Iterator[Type]. We don't want that. We need to retrieve a list that we can traverse inside the view. That's why you do findAll.toList . From there, the #select will map the pList and for each entry from the database have the id be associated to the patient name. Note that it is a string, because the Seq for the #Select tag requires Seq(String, String)