Databases are important. We need to keep all kinds of data in our applications – users, workspaces, messages, even things like selected UI theme. Yesod’s typical answer to this problem is Persistent. Persistent is a Haskell library for accessing databases – ranging from SQLite to MongoDB. It provides a type-safe access from Yesod to database and manages database schema and its changes (that’s actually done in runtime, but connection won’t be established without proper schema). Of course, not all migrations are possible to detect automatically – for example renaming a field will not be executed automatically. You can find more details about migrations in the Yesod Book, chapter about Persistent.

Luckily, our case is pretty straightforward – we want to store records for projects. For now we need only current state of the projects, so we’re going to use the usual facilities offered by Persistent. This involves some Template Haskell, but don’t worry, it’s quite straightforward. First we need to define our data type – but in a little special, Persistent-dedicated way. We’re gonna leave our pair-driven implementation and move to more complex data type. Our Project type will consist of a numeric identifier, textual name, short version of name and a deadline. I was quite tempted to leave out the deadline field since it introduces a bit of trouble, but after all – what’s a project without a deadline?

So, let’s create a Domain/Project.hs file with the following content:

module Domain.Project (Project(Project, projectIdentifier, projectName, projectShortName, projectDeadline)) where

import Import

share [mkPersist sqlSettings, mkMigrate "migrateProjects"] [persistLowerCase|
Project
  identifier Int
  name Text
  shortName Text
  deadline UTCTime
  deriving Show
|]

that’s quite dense, so probably requires some explaination. The part between | symbols is the actual data type definition. It is the input to the magic performed by Template Haskell, which results in generating field “accessors” exported in the first line: projectIdentifier, projectName, projectShortName, projectDeadline. You might guess that these are names of fields appended to name of data type and camelCased – there actually is a valid reason to do that – it is really helpful in avoiding name clashes (imagine field id). Actually, there is also a reason why the first field is called identifier and not simply id – Persistent internally creates an index field for each type, thus resulting in a clash. The type of this automatically generated id varies between backend databases – in most SQL-based ones it’s Int, but in MongoDB it’s String. Of course, both are nicely hidden under a type alias [[DatatypeName]]Id.

persistLowerCase is just a way of telling Persistent how to map field names to DB names, so let’s not focus on it. share is more interesting – it’s a helper function that takes a list of function an a quasiquotation as arguments (quasiquotes are these funny expressions strucured as [sth|sth else] – you can read more about them here) and passes the second one as argument for each of elements in the array. In our case these are mkPersist responsible for DB access and mkMigrate responsible for generating new DB schema.

And the last part that might be a little tricky – exports. module Domain.Project (Project(Project, projectIdentifier, projectName, projectShortName, projectDeadline)) – that’s just a way constructors and fields are exported in Haskell, TypeName(TypeConstructor, OtherTypeConstructor, field1, field2...).

Great, we’ve already done the tough job, now we have to clean up. First let’s change Database/Projects.hs. We’ve already prepared our application to interact with a database, but the interaction itself will be the subject of the next post. For now let’s just make it work. Here’s the new content:

module Database.Projects (projects) where
import Import

import Data.Time.Clock
import Domain.Project

time:: Integer -> Integer -> UTCTime
time a b = UTCTime (fromGregorian a 1 1) (secondsToDiffTime b)

projects::[Project]
projects = [Project 1 "Project 1" "P1" $ time 0 0,
  Project 3 "Substantial" "P2"  $ time 1 0,
  Project 5 "What the hell" "WTH"  $ time 100 0,
  Project 10 "The last project" "TLP"  $ time 10 10]

Only tricky thing here is the UTCTime creation. for some reason secondsToDiffTime is not imported by default, thus we need to explicitly add import Data.Time.Clock. This time function is also quite weird, but I guess we can live with that for now – after all we’ll get rid of such syntax in this file in another post (and if we won’t, we’ll refactor it soon).

Minor fixes are also needed in Handler/Project.hs (mostly substituting fst x with projectIdentifier x) and templates/projects.hamlet (same story + pattern matching in $forall). Handler/Projects.hs might also need a little change, namely importing Domain/Project.hs into the scope.

I’ve also removed copyright from the footer. We don’t need it now and it looked awful.

Quite some changes today! Remember that if something doesn’t work for you you can always check out the exact code that I’ve use during writing this post – it’s available at GitHub.

Next episode, in which we’re going to really attach a database to the application comes soon.

Stay tuned!