Some people find it trickier to store UUID values in their database with Persistent or to use UUID values in a Yesod web application than is really necessary. Here I'll share some code from my work that demonstrates some patterns in applications that use Persistent or Yesod which should make it easier.
The context for this post can be found in these two links:
Alternate title = Same as the original, but with: "and no Control.Lens needed" tacked on.
This code is adapted from stuff we've written at work.
Persistent / UUID integration
Radioactive dumping ground for orphan instances. Adding the instances makes Persistent understand how to serialize and deserialize the UUID type. The orphans can be avoided if you use a
-- Note we're taking advantage of -- PostgreSQL understanding UUID values, -- thus "PersistDbSpecific" instance PersistField UUID where toPersistValue u = PersistDbSpecific . B8.pack . UUID.toString $ u fromPersistValue (PersistDbSpecific t) = case UUID.fromString $ B8.unpack t of Just x -> Right x Nothing -> Left "Invalid UUID" fromPersistValue _ = Left "Not PersistDBSpecific" instance PersistFieldSql UUID where sqlType _ = SqlOther "uuid"
This is where we actually use the UUID type in our models.
module MyCompany.DB.Models where share [mkPersist sqlSettings,mkMigrate "migration"] [persistLowerCase| User json sql=users email Email sqltype=text UniqUserEmail email uuid UUID sqltype=uuid default=uuid_generate_v4() UniqUserUuid uuid deriving Show Read Eq Typeable |]
We use the default
JSON representation generated so that the format is predictable for the datatypes. I was a little queasy with this initially and it does mean we have to watch what happens to Aeson, but I believe net-net it reduces defects that reach production.
Yesod PathPiece integration
PathPiece is the typeclass Yesod uses to deserialize
Text data into a more structured type, so that something like the following:
!/#Subdomain/#NumberedSlug SomeRouteR GET
could work. Here
NumberedSlug are domain-specific types we made to represent a concept in our application in a type-safe manner.
PathPiece often goes unnoticed by people new to Yesod, but it's good to understand. For a super simple example:
newtype Subdomain = Subdomain Text deriving (Eq, Show, Read) instance PathPiece Subdomain where toPathPiece (Subdomain t) = t fromPathPiece = Just . Subdomain
The PathPiece class itself isn't terribly complicated or interesting:
-- https://hackage.haskell.org/package/path-pieces-0.2.1/docs/Web-PathPieces.html -- S for "Strict" class PathPiece s where fromPathPiece :: S.Text -> Maybe s toPathPiece :: s -> S.Text
To address the original post's code, I wouldn't have written that myself. I generally keep DB/IO stuff apart from things like forms. Partly this is because our web app repository is separate from our domain types / DB stuff repo, which sort of forces us to refactor things we might need to do more than once, or in a context that isn't a web app. The use of applicative style and the double-lifting was not idiomatic.
Alternate title rejected for being too snarky: I told the doctor it hurts when I move my arm a certain way. The doctor told me to stop moving my arm like that.