创建URL别名或使Deep-Urls漂亮

时间:2014-01-25 05:54:48

标签: haskell yesod

我有相当深的网址ID,我想知道我是否可以将它们转换为更好看的东西。我试着研究Slugs是如何为Yesod博客(https://github.com/yesodweb/yesod/wiki/Slugs)做的,但不知道我是否知道如何将其翻译成我在这里寻找的东西。

假设我想要显示Top Fiction Books,我有一个如下资源:

/topbooks/bookcategory/#BookCategoryId

如果我去/topbooks/bookcategory/1我会收到小说书,如果我到/topbooks/bookcategory/2我可能会得到非小说等等。

我的所有处理程序都使用数据库查询中的#BookCategoryId输入参数来获取相应的记录。

理想情况下,我想创建一个类似于:/topbooks/fiction/topbooks/non-fiction等的网址。如果我创建路由为/topbooks/#Text,我可以模式匹配字符串并返回Key。但是,我将不得不使用#BookCategoryId在每个处理程序中手动转换它。请注意,ID用作外键,因此依赖于getBy就像在Slug示例中完成它一样有点麻烦。

所以我想知道是否有更好的方法:是否可以定义类似于Slug的自定义类型,而不是仅仅将值转换为/来自Text / String,实际输出{{1 }}?这样我就可以直接在查询中使用参数。

更新:

澄清Michael的评论:

我知道如果不进行数据库查找,我们就无法获取ID。事实上,对于这个例子,我很难对查询机制进行编码。我只是想看看IDs机制是否会以某种方式简化转换过程。

例如,如果像这样的东西有效,那么它会没问题,但当然我会在编译器期待BookCategories时尝试返回Key时会出现类型错误。

PathPiece

当然我可以返回 data BookCategories = FICTION | NONFICTION instance PathPiece BookCategories where toPathPiece (FICTION) = T.pack "fiction" toPathPiece (NONFICTION) = T.pack "nonfiction" fromPathPiece s = let ups = map toUpper $ T.unpack s in case reads ups of [(FICTION, "")] -> Just $ Key $ PersistInt64 1 [(NONFICTION, "")] -> Just $ Key $ PersistInt64 [] -> Nothing otherwise -> Nothing 并在我的处理程序中解开它。这与使用签名Just FICTION的函数直接在Text上实际模式匹配在概念上没有什么不同。

Text -> BookCategoryId

如果我切换到文字输入

getBookCategoryR :: BookCategoryId -> Handler Html
getBookCategoryR bcId = do
     -- Normal use case when IDs are used in the URL
     books <- runDB $ selectList [ModelBookCategory ==. bcId] []

单行转换代码是我想要避免的。 getBookCategoryR :: Text -> Handler Html getBookCategoryR bc = do bcId = convertToId (bc) -- This is the line I am trying to avoid everywhere books <- runDB $ selectList [ModelBookCategory ==. bcId] [] 已经很好地处理了基于id的url并保持代码干净。如果有办法让Ids通过一些Type魔法返回,那么它会很棒。由于Haskell知识有限,我不知道它是否可行。

希望我的问题现在更清楚了。

谢谢!

1 个答案:

答案 0 :(得分:2)

不,没有这样的方法可以做到这一点,原因很简单:没有咨询数据库,就无法知道foo是否作为一个slug存在,如果存在,它是哪个ID涉及到。您将始终必须执行一些数据库操作以将slug转换为ID。


更新我仍然不确定我理解你在寻找什么,但关于PathPiece的简短回答是它只适用于转换,没有什么有副作用。如果你想编写像Text -> Handler BookCategoryId这样的函数,你当然可以这样做。如果你真的想要,你甚至可以用类型类抽象它,虽然我不确定你是否会获得任何东西。

这可能是错误的树,但这里有一个简短的想法,可能会激发你一点点:你可以为每个文本slug字段创建不同的newtype包装器,然后创建一个类型类来转换文本slug字段到适当的实体,例如:

newtype BookCatSlug = BookCatSlug Text
    deriving PathPiece

BookCategory
    slug BookCatSlug
    title Text
    ...
    UniqueBookCat slug

class Slug slug where
    type SlugEntity slug
    lookupSlug :: slug -> YesodDB App (Maybe (Entity (SlugEntity slug)))

instance Slug BookCatSlug where
    type SlugEntity BookCatSlug = BookCategory
    lookupSlug = getBy . UniqueBookCat

lookupSlug404 slug = runDB (lookupSlug slug) >>= maybe notFound return

myHandler slug = do
    Entity bookCatId bookCat <- lookupSlug404 slug

这些方面的东西应该有效,但我不确定“类型魔术”是否值得,因为有一个辅助函数并手动传入适当的唯一构造函数对于调用站点几乎一样容易并导致更简单的错误信息。