Hitting a catch 22 situation due to lack of generics support in Play template layer.
I have several shopping cart screens that all require a User and a Payment + optional custom fields.
case class Conference(
user: User,
payment: Payment
... custom fields here
)
So, rather than duplicating all the user and payment form fields per shop cart model, I've consolidated as above and implemented nested forms.
Now, the problem occurs at the template layer where there is to-date no generics support.
The parent/container form pulls in nested child forms like so:
#(_form: Form[Conference])
#user.nested( UserForm.form.fill(_form.get.user) )
#payment.nested( PaymentForm.form.fill(_form.get.payment) )
and then the child User form looks like:
#(_form: Form[User])
#inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
#inputText(_form("user.lastName"), '_label-> "Last Name*", 'class-> "required")
...
and the User model:
case class User(firstName: String, lastName: String ...)
How do I access "user.firstName", "user.lastName", etc. when there is no user property in the User model? The Play Form apply method is:
def apply(key: String): Field = Field(
this,
key,
constraints.get(key).getOrElse(Nil),
formats.get(key),
errors.collect { case e if e.key == key => e },
data.get(key))
Basically it's going to look for property data.user.firstName which is obviously not going to work.
I've thought about adding a user property to User model:
case class User(firstName: String, lastName: String ...) {
val user: User
}
but not sure if that will work and/or wreak havoc with case class companion object apply/unapply.
At any rate, given the lack of generics, what's a viable solution to the problem?
Were generics supported we could pass in an upper bound and everything would be rosy:
trait CartOrder {
user: User,
payment: Payment
}
case class Conference(...) extends CartOrder
and then nested User form is passed an instance that contains a user property and we're good
#[T <: CartOrder](_form: Form[T])
#inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
...
If type safety isn't a concern (Forms aren't all that type safe to begin with), you can just use Form[_] as the parameter type for your nested templates.
If you do want type safety, one option is to make a wrapper class for Form that is covariant and use that in place of Form. One implementation is:
package views.html
import play.api.data._
import play.api.libs.json.JsValue
object FormView {
implicit def formToFormView[A, T >: A](form: Form[A]): FormView[T] = new FormView[T] {
type F = A
def realForm = form
}
}
trait FormView[+T] {
type F <: T
def realForm: Form[F]
def apply(key: String): Field = realForm(key)
def constraints : Map[String, Seq[(String, Seq[Any])]] = realForm.constraints
def data: Map[String, String] = realForm.data
def error(key: String): Option[FormError] = realForm.error(key)
def errors(key: String): Seq[FormError] = realForm.errors(key)
def errors: Seq[FormError] = realForm.errors
def errorsAsJson: JsValue = realForm.errorsAsJson
def get: T = realForm.get
def formats: Map[String, (String, Seq[Any])] = realForm.formats
def globalError: Option[FormError] = realForm.globalError
def globalErrors: Seq[FormError] = realForm.globalErrors
def hasErrors: Boolean = realForm.hasErrors
def hasGlobalErrors: Boolean = realForm.hasGlobalErrors
override def hashCode: Int = realForm.hashCode
def mapping: Mapping[F] = realForm.mapping
def value: Option[T] = realForm.value
}
Now instead of your templates being
#(_form: Form[CartOrder])
which won't work because of invariance, you can use
#(_form: FormView[CartOrder])
and you can simply pass in any Form[T] where T is a subtype of CartOrder like
#user.nested(_form)
the implicits will handle the conversion from Form to FormView
A complete example can be found at: https://github.com/thatsmydoing/formtest
Ok, the dilly-o follows thusly (chime in if you have a better way):
play.api.data.Form[T] is invariant, so no dice passing in the super type of Conference (i.e. CartOrder) to the User form. In other words, this blows up:
// user.scala.html
#(_form: Form[CartOrder])
Basically you have to pass in an instance that itself is Form mappable.
To workaround the template layer fun house, I have implemented the following hack:
case class CartModel(user: User, payment: Payment)
EDIT
gets better, added in a bind helper to CartForm mapper below, which makes for clean syntax in views
object CartForm {
import play.api.data.Forms._
val form =
Form( mapping(
'user -> UserForm.mapper,
'payment -> PaymentForm.mapper )(CartModel.apply)(CartModel.unapply) )
// hey, generics!
def bind[T <: Form[_ <: CartContract]](t: T) =
t.value map{x=> form.fill( CartModel(x.user, x.payment) )} getOrElse form
}
and then in the Conference parent form, pull in User form fields like so:
#user.nested( CartForm.bind(_form) )
and the User form then receives:
#(_form: Form[CartModel])
A good deal of boilerplate is eliminated with nested forms in general, so overall, progress. Would be great not to rely on the intermediary Form mapper, but this is as good as I can come up with for now...
Guess you want
#(_form: Form[_ <: CartOrder])
instead of suggested
#[T <: CartOrder](_form: Form[T])
Related
I have a method definitions like so
def batchCacheable[T: ClassTag](cacheKeys: Seq[CacheKey])(callServiceToFetchValues: Seq[CacheKey] => Try[Map[CacheKey, T]]): Try[Map[CacheKey, T]]
Where CacheKey is a trait defined as having a single method called buildCacheKey, and I have a case class that extends that trait and has a id in it as well.
trait CacheKey {
def buildCacheKey: String
}
case class IDCacheKey(id: String) extends CacheKey {
override def buildCacheKey: String = {
s"CacheKey:$stringId"
}
}
And the function that I want to use for callServiceToFetchValues needs IDCacheKey to get the Id, it looks like this.
private def getStringsFromLMS(cacheKeys: Seq[CacheKey]): Try[Map[CacheKey, String]] = {
cacheKeys.map(_ -> _.Id)
}
So that it returns a map of Keys -> Ids. The problem is that batchCacheable can only pass into it a Seq of CacheKey, but I need it to be a Seq of IDCacheKey for the Id. How can I do this?
A straightforward solution would be to do something like this:
def getStringsFromLMS(cacheKeys: Seq[CacheKey]): Try[Map[CacheKey, String]] = {
cacheKeys.collect { case k: IDCacheKey => k.id }
}
This will silently ignore everything that's not IDCacheKey. If you'd rather throw an error in case there are keys of wrong type in the input, just replace .collect with .map.
Either way, this is not the right solution. The function declared to expect CacheKey should be able to handle any instance of CacheKey, no matter what type.
At a more general level, a CacheKey that doesn't provide enough identity information to fetch the value is not very useful.
Long story short, it looks like your CacheKey trait needs an abstract id method. That would solve your problem in a natural (and "correct") way.
Alternatively, you could further parameterize batchCacheable:
def batchCacheable[T, K <: CacheKey](cacheKeys: Seq[K])(
callServiceToFetchValues: Seq[K] => Try[Map[K, T]]
): Try[Map[K, T]]
This lets you declare getStringsFromLMS to accept IDCacheKey, and still be usable with batchCacheable
I am testing Deadbolt in a Scala Play Application. My controller methods look something like this:
def getProject(projectId: Int) = actionBuilder.RestrictAction("user").defaultHandler() {
authRequest =>
//retrieves project
}
In this case, I only want the user to be authorized to get the project if the projectId belongs to this user. Other more complex cases involve multiple parameters both from the query string and/or the post body.
From what I understand, the approach here would be to pass the parameters to a DynamicResourceHandler and then handling the permissions for each case individually. It is feasible, but I was expecting a bit more support from Deadbolt for this use case. What is the best approach to authorize a request based on the parameters received?
Deadbolt stays fairly neutral to avoid forcing developers into a specific style, but in this case you can use the meta argument to pass the information into the constraint. The definition is
object DynamicAction {
def apply(name: String, meta: Option[Any] = None): DynamicAction.DynamicActionBuilder = DynamicActionBuilder(name, meta)
case class DynamicActionBuilder(name: String, meta: Option[Any] = None) extends DeadboltActionBuilder {
override def apply[A](bodyParser: BodyParser[A])(block: AuthenticatedRequest[A] => Future[Result])(implicit handler: DeadboltHandler) : Action[A] =
deadboltActions.Dynamic(name, meta, handler)(bodyParser)(block)
}
}
so your controller function will look something like this
def getProject(projectId: Int) = actionBuilder.DynamicAction(name = "checkProject", meta = Some(projectId)).defaultHandler() {
authRequest =>
//retrieves project
}
This will get a DynamicResourceHandler (I've come to hate that name, I may change it in some future version) and invoke your implementation of this function
def isAllowed[A](name: String,
meta: Option[Any] = None,
deadboltHandler: DeadboltHandler,
request: AuthenticatedRequest[A]): Future[Boolean]
You will need to use asInstanceOf for the meta value.
For more complex requirements, you can pass whatever you have assembled the data into (a case class or map, for example) as the meta argument.
I have multiple case classes representing values in DB for ex User which saves user based properties like name / age / address and CallLog which saves timestamp / status_of_call
What i want to achieve
I want to have a helper function which accepts list of models and checks if the list is empty then returns "error" otherwise should return json array of the list.
My Approach
I want to have a trait which groups certain models in it and the helper method will accept either the trait or List of it in order to check or may be have a generic which implements the trait.
Problem
Since implicit writes are tightly coupled with the model class, compiler throws the error on the line Json.toJson(list)
Things i have tried
Kept implicit in trait and got recursive type error
I am scala noob pardon me if this sounds silly
Thanks in advance
Since User, CallLog, etc. will be serialized differently, Each Writes[T] will be different for each implementation of your Model trait, so a Writes[Model] has to know about the implementation it is trying to serialize.
It is therefore not possible to have it part of the Model trait, because this information isn't known yet when you define it.
A workaround in your case would be to define your Writes[Model] in the scope of your helper function instead.
An implementation of your helper function could be like this :
import play.api.libs.json.{JsValue, Json, Writes}
sealed trait Model
case class User(name: String, age: String, address: String) extends Model
object User {
implicit val userWrites = Json.writes[User]
}
case class CallLog(timestamp: String, status_of_call: String) extends Model
object CallLog {
implicit val callLogWrites = Json.writes[CallLog]
}
implicit val modelWrites = new Writes[Model] {
override def writes(o: Model): JsValue = o match {
case u: User => Json.toJson(u)
case cl: CallLog => Json.toJson(cl)
}
}
def helper(models: Model*): Either[JsValue, String] = models match {
case Nil => Right("Error")
case _ => Left(Json.toJson(models))
}
helper(User("John", "32", "..."))
helper(User("John", "32", "..."), CallLog("now", "In progress"))
I am working on a web server to process a registration form and save it to a datastore. I want this to be a library and work with different instances of registration forms.
I have the following:
trait RegistrationForm
case class SimpleRegistrationForm(name: String,
email: String,
username: String,
password: String) extends RegistrationForm
I want any subclass of RegistrationForm to be acceptable to my server.
I have the following traits:
/**
* Interface for saving to a datastore
*/
trait RegistrationDataStore[T <: RegistrationForm] {
def save(form: T): Either[DataStoreException, String]
}
/**
* Trait to handle the form.
*/
trait RegistrationService[T <: RegistrationForm] {
def handleForm(form: T): Either[AkkaHttpExtensionsException, String]
}
And an implementation:
class RegistrationServiceImpl[T <: RegistrationForm](
registrationDataStore: RegistrationDataStore[T])
extends RegistrationService[T] {
def handleForm(form: T): Either[AkkaHttpExtensionsException, String] = {
registrationDataStore.save(form)
}
My server basically looks like this:
object Server {
...
val registrationService = new RegistrationServiceImpl[SimpleRegistrationForm](
new RegistrationMockDataStore[SimpleRegistrationForm]())
...
}
How can I require that the same form subclass is passed as type parameters to implementations of both RegistrationService and RegistrationDataStore? At the moment, won't they both accept independent subclasses of RegistrationForm (I can't test because this doesn't yet compile)? E.g. will the following be valid, and if so, how can I prevent it?
val registrationService = new RegistrationServiceImpl[SimpleRegistrationForm](
new RegistrationMockDataStore[SomeOtherRegistrationForm]())
How can I force the compiler to make sure they are the same subclass of RegistrationForm?
No, it won't be valid. Your constructor parameter is registrationDataStore: RegistrationDataStore[T], so when you write new RegistrationServiceImpl[SimpleRegistrationForm](something), something must be a RegistrationDataStore[SimpleRegistrationForm].
Let's say I have a following case class:
case class Product(name: String, categoryId: Option[Long]/*, other fields....*/)
Here you can see that categoryId is optional.
Now let's say I have a following method in my DAO layer:
getCategoryProducts(): List[Product] = {
// query products that have categoryId defined
}
You see, that this method returns products, that are guaranteed to have categoryId defined with some value.
What I would like to do is something like this:
trait HasCategory {
def categoryId_!: Long
}
// and then specify in method signature
getCategoryProducts(): List[Product with HasCategory]
This will work, but then such a product will have two methods: categoryId_! and categoryId that smells bad.
Another way would be:
sealed trait Product {
def name: String
/*other fields*/
}
case class SimpleProduct(name: String, /*, other fields....*/) extends Product
case class ProductWithCategory(name: String, categoryId: Long/*, other fields....*/) extends Product
def getCategoryProducts: List[ProductWithCategory] = ...
This method helps to avoid duplicate methods categoryId and categoryId_!, but it requires you to create two case classes and a trait duplicating all the fields, which also smells.
My question: how can I use Scala type system to declare this specific case without these fields duplications ?
Not sure how much this will scale for your particular case, but one solution that comes to mind is to parameterize over the Option type using a higher-kinded generic type:
object Example {
import scala.language.higherKinds
type Id[A] = A
case class Product[C[_]](name: String, category: C[Long])
def productsWithoutCategories: List[Product[Option]] = ???
def productsWithCategories: List[Product[Id]] = ???
}
A way to do it is to use type classes -
import scala.language.implicitConversions
object Example {
sealed class CartId[T]
implicit object CartIdSomeWitness extends CartId[Some[Long]]
implicit object CartIdNoneWitness extends CartId[None.type]
implicit object CartIdPresentWitness extends CartId[Long]
case class Product[T: CartId](name: String, categoryId: T /*, other fields....*/)
val id: Long = 7
val withId = Product("dsds", id)
val withSomeId = Product("dsds", Some(id))
val withNoneId = Product("dsds", None)
val presentId: Long = withId.categoryId
val maybeId: Some[Long] = withSomeId.categoryId
val noneId: None.type = withNoneId.categoryId
val p = Product("sasa", true) //Error:(30, 18) could not find implicit value for evidence parameter of type com.novak.Program.CartId[Boolean]
}
This solution involves some code and dependent on implicits but does what you're trying to achieve.
Be aware that this solution is not completely sealed and can be 'hacked'. You can cheat and do something like -
val hack: Product[Boolean] = Product("a", true)(new CartId[Boolean])
val b: Boolean =hack.categoryId
For some more - advanced solutions which include
* Miles Sabin (#milessabin)’s Unboxed union types in Scala via the Curry-Howard isomorphism
* Scalaz / operator
http://eed3si9n.com/learning-scalaz/Coproducts.html