subjourney
group steps together inside a journey.
askList
allows a subjourney to loop, returning multiple items.
subjourneys
It is possible to group a series of steps into a subjourney with its own step ID.
import ltbs.uniform._
case class Customer(username: String, age: Int)
case class Order(customer: Customer, units: Int)
def placeOrder = for {
customer <- subJourney("customer") {
for {
u <- ask[String]("username")
a <- ask[Int]("age")
} yield Customer(u,a)
}
units <- ask[Int]("number-of-units")
} yield Order(customer, units)
In effect the step ID’s are hierarchical, rather than flat, however it is entirely at the discretion of the interpreter as to how subjourneys should be handled.
For example a web-based interpreter may choose to create a URL for the
username page under /customer/username
, whereas a CLI interpreter
may simply flatten the hierarchy and effectively inline the subjourney.
askList
Consider the situation where you want to ask
for a collection -
case class User(name: String, age: Int)
def journey = for {
p <- ask[List[User]]("users")
_ <- tell("confirm", p)
} yield p
Now imagine that you want to separate out asking for the name and age
into separate stages, but still returning List[User]
. For this
purpose you can use askList
.
def journey2 = for {
p <- askList[User]("users") {
case (_: Option[Int], _: List[User]) =>
for {
name <- ask[String]("name")
age <- ask[Int]("age")
} yield User.apply(name, age)
}
_ <- tell("confirm", p)
} yield p
There is no interact
equivalent for askList
as the interaction
will typically involve showing the user the collection as the user
constructs it.
Additionally as the interpreter may provide the mechanism to edit a record you may wish to pre-populate the steps with values from the collection.
For this purpose you can access the index of the record being edited
(or None
if the user is adding a record), and the collection of
records already in the collection.
def journey3 = for {
p <- askList[User]("users") {
case (editIndex: Option[Int], existing: List[User]) =>
val editRecord = editIndex.flatMap(existing.lift)
for {
name <- ask[String]("name",
default = editRecord.map(_.name)
)
age <- ask[Int]("age",
default = editRecord.map(_.age)
)
} yield User.apply(name, age)
}
_ <- tell("confirm", p)
} yield p
You could also use this for cross-record validation -
import ltbs.uniform.validation.Rule
def journey4 = for {
p <- askList[User]("users") {
case (editIndex: Option[Int], existing: List[User]) =>
val editRecord = editIndex.flatMap(existing.lift)
for {
name <- ask[String]("name",
validation = Rule.cond[String](
{n => !existing.exists(_.name == n)},
"duplicate-name"
),
default = editRecord.map(_.name)
)
age <- ask[Int]("age",
default = editRecord.map(_.age)
)
} yield User.apply(name, age)
}
_ <- tell("confirm", p)
} yield p