You can move one more story from the ready phase to the
dev phase. A pair of developers looking for new work can select a card from the ready
phase and move that card to the dev phase. Once the development work is done, the
card moves to the test phase where, in this stage, a tester, business analyst, or other
members of the team will verify the work against the user story. When the story is
approved or verified, it’s moved to the deploy phase, which means it’s ready for pro-
duction deployment. This is how a card (work) flows through the system.
As a customer, I want to create a new user story so I can add stories to the ready phase.
As a developer, I want to move cards (stories) from one phase to another so I can signal
progress.
sbt
1
2
3
4
5
6
7
8
// The following expression will create a Setting[String] setting:
> set name := "Testing SBT"
[info] Reapplying settings...
[info] Setcurrent project to Testing SBT
> set version := "1.0"
[info] Reapplying settings...
[info] Setcurrent project to Testing SBT
> session save
Settings are the way SBT stores the build definition. A build definition defines a list
of Setting[T] where Setting[T] is a transformation affecting SBT’s key value pair.
A Setting is created assigning value to SettingKey. There are three kinds of keys
in the SBT:
SettingKey[T] is a key with a value computed only once. Examples are
name or scalaVersion.
TaskKey[T] is a key with a value that has to be recomputed each time.
TaskKey is used to create tasks. Examples are compile and package.
InputTask[T] is a task key which takes command-line arguments as input.
All the available keys are defined in the sbt.Keys object, and it’s automatically
imported for you in the build.sbt file.
The third option is to use giter8 (https://github.com/n8han/giter8). It’s a com-
mand-line tool to generate files and directories from templates published in Github.
This is slowly becoming a standard way of creating projects in Scala. Once giter8 is
installed, you can choose a template to generate the project structure.
SBT project structure is recursive. The project directory is another project inside your
project that knows how to build your project.
The following is how you define dependency in SBT :
groupID % artifactID % version
If you use %% after groupID, SBT will add the Scala version of the proj-
ect to the artifact ID.
SBT can read the dependencies defined in the
POM file if you use the externalPom() method in your build file.
Alternatively, you can create a project definition file configured to use a local Maven
repository:
1
2
resolvers += "Local Maven Repository" at
"file://"+Path.userHome+"/.m2/repository"
Here’s how
the build.sbt looks after all the changes:
Another common thing you can do with SBT is create custom tasks for the project. For
custom tasks, the .scala build definition file is used because the .sbt build file doesn’t
support it. To create custom tasks follow these steps:
Create a TaskKey .
Provide a value for the TaskKey .
Put the task in the .scala build file under project.
Additionally, jetty-web is added into test scope.
The scope allows SBT keys to have values in more than one context.
This is useful for plug-ins because scoping allows plug-ins to
create tasks that don’t conflict with other task names.
To include all the tasks from the plug-in to your project,
you have to import the settings from the plug-in project into
your build.sbt file as follows:
seq(com.github.siasia.WebPlugin.webSettings :_*)
To start the web server, run the container:start task,
and it will start the Jetty server at port number 8080
<web-app><servlet><servlet-name>Scalaz</servlet-name><!-- This servlet will create both a request
and response of type scala.collection.Stream --><servlet-class>
scalaz.http.servlet.StreamStreamServlet
</servlet-class><init-param><param-name>application</param-name><param-value>
com.kanban.application.WeKanbanApplication
</param-value></init-param></servlet><servlet-mapping><servlet-name>Scalaz</servlet-name><url-pattern>/*</url-pattern></servlet-mapping></web-app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// StreamStreamServletApplication to create your application// class because it’s enforced by the// Scalaz servlet you’re using to handle all the HTTP request and response. finalclassWeKanbanApplicationextendsStreamStreamServletApplication {val application = new ServletApplication[Stream, Stream] {
def application(implicit servlet: HttpServlet,
servletRequest: HttpServletRequest,
request: Request[Stream]) = {
def found(x: Iterator[Byte]) : Response[Stream] = OK << x.toStream
HttpServlet.resource(found, NotFound.xhtml)
}
}
}
1
2
3
4
5
<html><body><h1>weKanban board will come shortly</h1></body></html>
classStory(val number: String, val title: String, val phase: String)package com.kanban.models
import org.squeryl._
// defines the schema with a table// called “STORIES” for your Story classobjectKanbanSchemaextendsSchema {val stories = table[Story]("STORIES")
}
import sbt._
import Keys._
objectH2TaskManager {var process: Option[Process] = None
// creates a new config name “h2” and extends the Compile config// The Compile config will provide the necessary// classpath setting you need to run the tasks.lazyval H2 = config("h2") extend(Compile)
val startH2 = TaskKey[Unit]("start", "Starts H2 database")
// <<= method in SBT helps to create a// new setting that depends on other settings.val startH2Task = startH2 in H2 <<= (fullClasspath in Compile) map {
cp =>
startDatabase {
cp.map(_.data)
.map(_.getAbsolutePath())
.filter(_.contains("h2database"))
}
}
def startDatabase(paths: Seq[String]) = {
process match {
case None =>
val cp = paths.mkString(System.getProperty("path.separator"))
val command = "java -cp " + cp + " org.h2.tools.Server"
println("Starting Database with command: " + command)
process = Some(Process(command).run())
println("Database started ! ")
case Some(_) =>
println("H2 Database already started")
}
}
val stopH2 = TaskKey[Unit]("stop", "Stops H2 database")
val stopH2Task = stopH2 in H2 :={
process match {
case None => println("Database already stopped")
case Some(_) =>
println("Stopping database...")
process.foreach{_.destroy()}
process = None
println("Database stopped...")
}
}
}
objectMainBuildextendsBuild {import H2TaskManager._
lazyval scalazVersion = "6.0.3"lazyval jettyVersion = "7.3.0.v20110203"lazyval wekanban = Project( "wekanban",
file(".")).settings(startH2Task, stopH2Task)
}
importimport scalaz._
import Scalaz._
import scalaz.http._
import response._
import request._
import servlet._
import HttpServlet._
import Slinky._
import com.kanban.views._
import com.kanban.models._
finalclassWeKanbanApplicationextendsStreamStreamServletApplication {import Request._
import Response._
implicit val charset = UTF8
// To read POST parameters from the request// use ! POST generally means a side-effectdef param_!(name: String)(implicit request: Request[Stream]) =
(request | name).getOrElse(List[Char]()).mkString("")
// return parameter value as stringdef param(name: String)(implicit request: Request[Stream]) =
(request ! name).getOrElse(List[Char]()).mkString("")
def handle(implicit request: Request[Stream],
servletRequest: HttpServletRequest): Option[Response[Stream]] = {
request match {
case MethodParts(GET, "card" :: "create" :: Nil) =>
Some(OK(ContentType, "text/html") << strict <<
CreateStory(param("message")))
case MethodParts(POST, "card" :: "save" :: Nil) =>
Some(saveStory)
case MethodParts(GET, "kanban" :: "board" :: Nil) =>
Some(OK(ContentType, "text/html") << transitional << KanbanBoard())
case _ => None
}
}
privatedef saveStory(implicit request: Request[Stream],
servletRequest: HttpServletRequest) = {
val title = param_!("title")
val number = param_!("storyNumber")
Story(number, title).save match {
case Right(message) =>
redirects[Stream, Stream]("/card/create", ("message", message))
case Left(error) => OK(ContentType, "text/html") << strict <<
CreateStory(error.toString)
}
}
// The Scalaz core provides a method called | for the Option class and// using it you can combine both handle and resource methods so that when the han-// dle method returns None you can invoke the resource method as a fallback to load// resources. val application = new ServletApplication[Stream, Stream] {
def application(implicit servlet: HttpServlet,
servletRequest: HttpServletRequest, request: Request[Stream]) = {
def found(x: Iterator[Byte]) : Response[Stream] = OK << x.toStream
handle | resource(found, NotFound.xhtml)
}
}
}
// story model
// At this point Squeryl has only created the query—it hasn’t executed it in the database. It will execute the query the
// moment you try to access the first element in the collection.
//by invoking the map method, so that you access these instances of Story objects outside the transaction
def findAllByPhase(phase: String) = tx {
from(stories)(s => where(s.phase === phase) select(s)) map(s => s)
}