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!
code
more code
~~~~