This morning I was following along with this interesting tutorial on using Servant to build a simple API server.
At the end of the tutorial, the author suggests adding a Blog type, so I figured I would give it a shot, but I got stuck trying to implement and serialize a foreign key relationship that extends upon the logic in the tutorial (perhaps an important disclosure here: I'm new to both Servant and Persistent).
Here are my Persistent definitions (I added the Post
):
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
name String
email String
deriving Show
Post
title String
user UserId
summary String
content String
deriving Show
|]
The tutorial builds a separate Person
data type for the Servant API, so I added one called Article
as well:
-- API Data Types
data Person = Person
{ name :: String
, email :: String
} deriving (Eq, Show, Generic)
data Article = Article
{ title :: String
, author :: Person
, summary :: String
, content :: String
} deriving (Eq, Show, Generic)
instance ToJSON Person
instance FromJSON Person
instance ToJSON Article
instance FromJSON Article
userToPerson :: User -> Person
userToPerson User{..} = Person { name = userName, email = userEmail }
Now, however, when I attempt to create a function that turns a Post
into an Article
, I get stuck trying to deal with the User
foreign key:
postToArticle :: Post -> Article
postToArticle Post{..} = Article {
title = postTitle
, author = userToPerson postUser -- this fails
, summary = postSummary
, content = postContent
}
I tried a number of things, but the above seemed to be close to the direction I'd like to go in. It doesn't compile, however, due to the following the error:
Couldn't match expected type ‘User’
with actual type ‘persistent-2.2.2:Database.Persist.Class.PersistEntity.Key
User’
In the first argument of ‘userToPerson’, namely ‘postUser’
In the ‘author’ field of a record
Ultimately, I'm not really sure what a PersistEntity.Key User
really is and my errant googling has not gotten me much closer.
How do I deal with this foreign-key relationship?
Working Version
Edited with an answer thanks to haoformayor
postToArticle :: MonadIO m => Post -> SqlPersistT m (Maybe Article)
postToArticle Post{..} = do
authorMaybe <- selectFirst [UserId ==. postUser] []
return $ case authorMaybe of
Just (Entity _ author) ->
Just Article {
title = postTitle
, author = userToPerson author
, summary = postSummary
, content = postContent
}
Nothing ->
Nothing
For some record type
r
,Entity r
is the datatype containingKey r
andr
. You can think of it as a dollied-up tuple(Key r, r)
.(You might wonder what
Key r
is. Different backends have different kinds ofKey r
. For Postgres it'll be a 64-bit integer. For MongoDB there are object IDs. The documentation goes into more detail. It's an abstraction that allows Persistent to support multiple datastores.)Your problem here is that you have a
Key User
. Our strategy will be to get you anEntity User
, from which we'll be able to pull out aUser
. Fortunately, going fromKey User
toEntity User
is easy with aselectFirst
– a trip to the database. And going fromEntity User
toUser
is one pattern match.Gross, more generic version
We assumed a SQL backend above, but that function also has the more generic type
Which you might need if you're not using a SQL backend.