I'm building a Scala Play app where events and data are persisted in Json format, and I'm trying to model users and the roles they're assigned. My thinking has been to model Roles as case objects, as each standard role only needs defining once for all users in the application, and I'd like to pattern match on the type of role a particular user has been assigned. So far, I have this;
package models
abstract class User {
def displayName: String
def role: Role
}
case class GuestUser(displayName: String, role: Role) extends User {
}
case class RegisteredUser(displayName: String, role: Role) extends User {
}
trait Role { // have tried abstract class too - but like idea of developing stackable traits for role permissions
}
object Role {
implicit val RoleTypeFormat: Format[Role] = Json.format[Role]
def apply(className: String): Role = Class.forName(className: String).asInstanceOf[Role]
def unapply(role: Role): Option[String] = Option(this.getClass.getName) // have also tried .getSimpleName
}
case object GuestUserRole extends Role {
}
case object RegisteredUserRole extends Role {
}
If I don't define an apply and unapply method in object Role
, and rely only on the implicit value that uses Json.format[Role]
, I get a 'no apply function found' or 'no unapply function found' error - so I added them, to try and get rid of this error.
I couldn't get it to compile without adding .asInstanceOf[Role]
to the Role
apply method. It now compiles, but when I try to set the role: Role
parameter of a new RegisteredUser
instance using,
val role: Role = RegisteredUserRole
a new RegisteredUser
instance is created, where the role property gets serialized to Json as;
"role":{"className":"models.Role$”}
But when I try to deserialize it, I get Exception in thread "pool-4868-thread-1" java.lang.ClassCastException: java.lang.Class cannot be cast to models.Role
My aim is to end up with the same RegisteredUser
instance (or GuestUser
instance), so I can do pattern matching in the view controllers, along the lines of;
def isAuthorized: Boolean = {
role match {
case RegisteredUserRole => true
case GuestUserRole => false
// etc
}
}
Any help and advice on this would be very much appreciated. I'm not yet skilled and knowledgeable enough in Scala and Play to know whether I'm even on the right track with modelling Users and Roles.
As @lmm suggested, it would be better to provide a custom Format[Role]
rather than trying to create instances in a weird way.
Something like this:
implicit val fmt = new Format[Role] {
def reads(js: JsValue): JsResult[Role] = {
js.validate[String] fold (
error => JsError(error),
role => role match {
case "GuestUserRole" => JsSuccess(GuestUserRole)
case "RegisteredUserRole" => JsSuccess(RegisteredUserRole)
case _ => JsError(Nil) // Should probably contain some sort of `ValidationError`
}
)
}
def writes(r: Role): JsValue = JsString(r.toString)
}