enforcing constraints
The test data generator described in the previous section will handle most any persistent types you put in your domain. One thing we cannot handle out of the box is when exceptions are thrown in the constructors of your persistent and component objects.
A class constructor is a great place to enforce domain constraints, such as requiring that an email
has an at sign ('@'
):
import longevity.model.annotations.component
@component[DomainModel]
case class Email(email: String) {
if (!email.contains('@'))
throw new ConstraintValidationException("no '@' in email")
}
The test data generator uses ScalaCheck’s Arbitrary
and Gen
,
scalacheck-shapeless to generate your
test data. An Arbitrary
is produced for you when the @persistent
, @polyPersistent
, or
@derivedPersistent
macro annotations are expanded. To override all or parts of the provided
Arbitrary
, you need to provide your own implicit Arbitrary
in the same package as your
persistent or component. For example, we can provide the following implicits in your package.scala
for constructing Emails
that match our constraint:
import org.scalacheck._
import org.scalacheck.Arbitrary.arbitrary
def genEmail = for {
lhs <- arbitrary[String]
rhs <- arbitrary[String]
} yield Email(s"$lhs@$rhs.com")
implicit val arbitraryEmail = Arbitrary(genEmail)
Extending this example, we have a persistent object that has both a primary email, and a set of all emails. Our constraint is that the primary email occurs in the set:
import longevity.model.annotations.persistent
@persistent[DomainModel]
case class ComplexConstraint(
id: ComplexConstraintId,
primaryEmail: Email,
emails: Set[Email]) {
if (!emails.contains(primaryEmail))
throw new ConstraintValidationException("primary email is not in emails")
}
In the corresponding package.scala
, we provide an Arbitrary[ComplexConstraint]
that produces a
set of emails that includes the primary email:
import org.scalacheck._
import org.scalacheck.Arbitrary.arbitrary
def genComplexConstraint = for {
id <- arbitrary[ComplexConstraintId]
primary <- arbitrary[Email]
secondaries <- arbitrary[Set[Email]]
} yield ComplexConstraint(id, primary, secondaries + primary)
implicit val arbitraryComplexConstraint = Arbitrary(genComplexConstraint)