Question

I'm using Constraints on my web forms and I've noticed that several forms have similar validations, for instance I have several types of form with a start date and an end date. In each case, I want to validate that the start date is before the end date. Here's the case class I'm creating from my form:

   case class OrderSearchForm(orderId: Option[Int], startDate:Option[Long], endDate:Option[Long])

and my validation (Let's ignore the .get() for now):

  def validateSearchDate = Constraint[OrderSearchForm]{ 
    osf: OrderSearchForm => {
      if (!osf.startDate.isEmpty && !osf.endDate.isEmpty && osf.startDate.get.compareTo(osf.endDate.get) > 0 )
        Invalid("Begin Date is after End Date.")
      else
        Valid
    }
  }

Now, since I have lots of forms with a start date and an end date, I'd like to re-write my validation to work with all of the case classes representing these forms. I'm wondering whether the typeclass pattern can help me with this:

trait TwoDates[T] { 
  def twoDatesTuple(t: T): (Option[Long], Option[Long]) 
}

trait TwoDatesOSF extends TwoDates[OrderSearchForm] { 
  def twoDatesTuple(t: OrderSearchForm) = (t.startDate, t.endDate) 
}

implicit object TwoDatesOSF extends trait TwoDatesOSF

def validateSearchDate = Constraint[TwoDates[_]] { t: TwoDates[_] => ... (as above)}

but applying does not work:

validateSearchDate(OrderSearchForm(None, None, None))

yields:

error: type mismatch; found : OrderSearchForm required: TwoDates[_] betweenDates(osf)

1) Can I write generic validations using typeclasses? If so, what am I doing wrong?

2) Can I write generic validations while AVOIDING using super-classes (i.e.

abstract class TwoDates(start: Option[Long], end:Option[Long])

case class OrderSearchForm(orderId: Option[String], startDate:Option[Long], endDate:Option[Long]) extends TwoDates(startDate, endDate) 

which seems awkward once multiple validations are in play)

Thanks!

Was it helpful?

Solution

I think you can use structural types:

private type TwoDates = { def startDate: Option[Date]; def endDate: Option[Date] }

def validateTwoDates = Constraint[TwoDates] { osf: TwoDates =>
if (!osf.startDate.isEmpty && 
    !osf.endDate.isEmpty && 
    osf.startDate.get.compareTo(osf.endDate.get) > 0) {
        Invalid("Begin Date is after End Date.")
    } else Valid
}

case class Something(
   startDate: Option[Date], 
   endDate: Option[Date], 
   name: String)

private val form = Form(mapping(
   "startDate" -> optional(date),
   "endDate" -> optional(date),
   "name" -> text)
(Something.apply)(Something.unapply).verifying(validateTwoDates))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top