json之使用 Servant 和 Persistent 表示 JSON 中的外键关系
今天早上我跟着this interesting tutorial关于使用 Servant 构建一个简单的 API 服务器。
在教程的最后,作者建议添加一个博客类型,所以我想我会试一试,但我在尝试实现和序列化扩展到教程中的逻辑的外键关系时遇到了困难 (也许这里有一个重要的披露:我对 Servant 和 Persistent 都是新手)。
这是我的 Persistent 定义(我添加了 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
|]
本教程为 Servant API 构建了一个单独的 Person 数据类型,因此我也添加了一个名为 Article 的数据类型:
-- 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 }
但是,现在,当我尝试创建一个将 Post 转换为 Article 的函数时,我在尝试处理 User外键:
postToArticle :: Post -> Article
postToArticle Post{..} = Article {
title = postTitle
, author = userToPerson postUser -- this fails
, summary = postSummary
, content = postContent
}
我尝试了很多东西,但上面的似乎接近我想进入的方向。它没有编译,但是,由于以下错误:
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
最终,我不太确定 PersistEntity.Key User 到底是什么,而且我错误的谷歌搜索并没有让我更接近。
如何处理这种外键关系?
工作版本
感谢 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
请您参考如下方法:
对于某些记录类型r,Entity r是包含 Key r 和 r 的数据类型。您可以将其视为一个拼凑而成的元组 (Key r, r)。
(您可能想知道什么是 Key r。不同的后端有不同类型的 Key r。对于 Postgres,它将是一个 64 位整数。对于 MongoDB,有是对象 ID。documentation goes into more detail。它是允许 Persistent 支持多个数据存储的抽象。)
这里的问题是您有一个关键用户。我们的策略是为您提供一个 Entity User,我们可以从中提取一个 User。幸运的是,使用 selectFirst 从 Key User 到 Entity User 很容易——访问数据库。从 Entity User 到 User 是一种模式匹配。
postToArticle :: MonadIO m => Post -> SqlPersistT m (Maybe Article)
postToArticle Post{..} = do
authorMaybe <- selectFirst [UserId ==. postUser] []
return $ case authorMaybe of
Just (Entity _ author) ->
Article {
title = postTitle
, author = author
, summary = postSummary
, content = postContent
}
Nothing ->
Nothing
恶心,更通用的版本
我们假设上面有一个 SQL 后端,但该函数也有更通用的类型
postToArticle ::
(MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val) =>
Post -> ReaderT backend m (Maybe Article)
如果您不使用 SQL 后端,您可能需要它。
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。



