I'm currently having issues with Salat. Hope you guys can help me!
Here's the case class that is driving me crazy:
object UserDAO extends SalatDAO[User, ObjectId](
collection = DB("users") //Returns the "users" MongoCollection
)
case class User(
_id: ObjectId = new ObjectId,
firstName: String,
lastName: String,
screenName: String,
phoneNumber: PhoneNumber,
validated: Boolean = false)
PhoneNumber is an instance of type com.google.i18n.phonenumbers.Phonenumber$PhoneNumber (I'm using libphonenumber)
This is my custom transformer:
class PhoneNumberTransformer extends CustomTransformer[PhoneNumber, String] {
val phoneNumberUtils = PhoneNumberUtil.getInstance()
def deserialize(b: String) = phoneNumberUtils.parse(b, "UK")
def serialize(a: PhoneNumber) = phoneNumberUtils.format(a, PhoneNumberFormat.INTERNATIONAL)
}
This is my custom context:
package object model {
implicit val ctx = new Context {
val name = "Custom Salat Context"
}
ctx.registerCustomTransformer(new PhoneNumberTransformer)
}
If I try to insert a new User document using UserDAO, I get this exception:
project java.lang.IllegalArgumentException: can't serialize class com.google.i18n.phonenumbers.Phonenumber$PhoneNumber
project at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:284)
project at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:185)
project at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:131)
project at com.mongodb.DefaultDBEncoder.writeObject(DefaultDBEncoder.java:33)
[...]
Any idea on how to solve this?
Thanks
Salat developer here. I'm not familiar with libphonenumber but this is most likely breaking down because it looks like you're trying to serialize an inner class.
Something to try. If you copy pasted the PhoneNumber class to the top level of a local package (not inside an object, trait, or class), extending the relevant class/interface that bring the i18n goodness, and changed the type param to point at this class instead, does it work?
If so, the problem is that Salat doesn't support inner classes. If not, we'll have to look further.
You cannot serialize Java classes directly to Salat. You need to write either a custom salat serializer or write PhoneNumber as a case class
Related
Is there a way to use "typesafe" parameters for action in play framework? Instead of userId being a String, it could be of a more typesafe class like in the example below:
case class UserId(v: String) extends AnyVal
object UsersController extends Controller {
def get(userId: UserId) = Action {
Ok(Users.find(userId))
}
}
This would also make the test-code more typesafe:
val userId: UserId = ....
FakeRequest(routes.UsersController.get(userId))
The code above would give a compiler error if you accidentally passed in something else.
...But to get there you would have to convert it to the correct type in the .routes-file somehow..?
You can use the PathBindable[A] trait.
http://julien.richard-foy.fr/blog/2012/04/09/how-to-implement-a-custom-pathbindable-with-play-2/
Implementing my domain model in scala using case classes I got
abstract class Entity {
val _id: Option[BSONObjectID]
val version: Option[BSONLong]
}
and several case classes defining the different entities like
case class Person (
_id: Option[BSONObjectID],
name: String,
version: Option[BSONLong]
) extends Entity
What I need is a way to set the _id and version later on from a generic method which operates on an Entity because I have to share this behavior over all Entities and want to avoid writing it down hundreds of times ;-). I would love to be able to
def createID(entity: Entity): Entity = {
entity.copy(_id = ..., version = ...)
}
...but of course this does not compile since an Entity has no copy-method. It is generated for each single case class by the compiler...
What is the best way to achieve this in scala?
To prevent somebody asking: I have to use case classes since this is what the third-party-library is extracting for me from the requests I get and the case class instances are what is serialized back to BSON / MongoDB later on...
Indeed one can find a way to implement something like this at
Create common trait for all case classes supporting copy(id=newId) method
but since it is quite complicated for my use case I would prefer just to create two new classes
class MongoId(var id : BSONObjectID = null) {
def generate = {
id = BSONObjectID.generate
}
}
class MongoVersion(var version: Long = 0) {
def update = {
version = System.currentTimeMillis
}
}
and implemented the shared behavior regarding these fields there. Of course you have to change the definition of your base class accordingly:
abstract class Entity {
def _id: MongoId
def version: MongoVersion
}
To make it clear: This works only if the behavior you want to share over several case classes does only affect (in my case changes) one attribute ...
Would implementing a trait work?
trait MongoIdHandler {
def createId(entity : Entity) : Option[BSONObjectID] = { ..}
def setVersion(version : String) : Unit = { ..}
}
case class Person (..) with MongoIdHandler ..
If any of the instances require specialized versions of the id generator they can override the 'default' impl provided by the trait.
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
I'm using Salat with MongoDB and I'm trying to convert to natural keys to avoid duplicates in the database. The case class I'm using looks somewhat like:
case class Foo(someRelatedId: String, email: String ...)
I would like to add a natural key that consists of someRelatedId+email and have MongoDB use that instead of the default ObjectId. From the documentation I get the feeling it is possible, but I'm still groping around for a working solution. This is in a large part due to my lack of proficiency with Scala itself, I'm sure.
Update: I have a working solution now, but I'm still wondering if it is the best way to go
case class Foo(someRelatedId: String, email: String, naturalKey: String)
object Foo {
def apply((someRelatedId: String, email: String) {
apply(someRelatedId, email, someRelatedId+email)
}
}
And then in package.scala I map to a custom salat context:
implicit val ctx = new Context() {
val name = Some("Custom Context")
}
ctx.registerGlobalKeyOverride(remapThis = "naturalKey", toThisInstead = "_id")
This way I avoid having a mandatory (meaningless) _id field in my domain classes, but I do have to overload apply() on the companion object, which seems a bit clunky.
main Salat developer here.
Like Milan suggested, create a case class for your composite key:
case class FooKey(someRelatedId: String, email: String)
case class Foo(#Key("_id") naturalKey: FooKey) {
// use #Persist if you want these fields serialized verbatim to Mongo - see https://github.com/novus/salat/wiki/Annotations for details
#Persist val email = naturalKey.email
#Persist val someRelatedId = naturalKey.someRelatedId
}
object FooDAO extends SalatDAO[Foo, FooKey](collection = /* some Mongo coll */ )
If you object to "_id" as a field name, you can use a global override in the context to remap "_id" to "naturalKey", or supply ad hoc #Key overrides on each object.
I don't personally like giving the _id a different name in your models as then your Mongo queries must use the serialized key "_id" while all your business logic must use the case class field name ("naturalKey" or whatever), but YMMV.
P.S. Our mailing list is at http://groups.google.com/group/scala-salat - I'll see your question quicker there than Stack Overflow.
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