I'm trying to setup a basic endpoint which takes an id and goes through a join table to return all joined records using persistent and spock, a working implementation looks like this.
get ("songs" <//> var) $ \id -> do
song <- getRecord404 $ SongKey id
tags <- runSQL $ select $
from $ \(tag `InnerJoin` songTag) -> do
on (songTag ^. SongTagTagId ==. tag ^. TagId)
where_ (songTag ^. SongTagSongId ==. val (SongKey id))
return (tag)
json $ object ["song" .= song, "tags" .= tags]
getRecord404 :: (PersistEntity a, PersistEntityBackend a ~ SqlBackend, MonadIO m0, Monad m, ActionCtxT ctx m0 ~ m, HasSpock m, SpockConn m ~ SqlBackend) => Key a -> m (Entity a)
getRecord404 k = do
res <- getRecord k
guardNotFound res
guardNotFound :: MonadIO m => Maybe (Entity a) -> ActionCtxT ctx m (Entity a)
guardNotFound Nothing = do
setStatus status404
text "Not found"
guardNotFound (Just a) = return a
Question 1: Does the class constraints have to be so large on these functions? It seems like trying to compose these monads would get tiresome quickly with so many constraints. I've seen that I can use Constraint Kinds to setup constraint synonyms, but I feel like maybe I'm doing something wrong to need so many constraints.
I also wanted to see if I could write a more generic method for performing the join operation. Presumably it would be sufficient information to have the input type, and the table used for the join, the could compiler could infer the output type and (at least in a language like ruby) you could inspect the join table type to find the proper columns to join on. Something like:
manyThrough :: (Monad m...) => Key a -> [Somehow pass the information about which table to use] -> m [Entity B]
Trying to implement such a function is beyond me though. I'm not sure what is the best way to pass the information about which table to use for the join. I implemented a version which passes the columns explicitly (below), which works but it again has a huge class constraint, and takes a larger method signature than I'd like. Is there an way to achieve something like the signature above?
manyThrough :: (PersistEntity t1, PersistEntity t2, PersistField (Key b) , PersistField (Key a), PersistEntityBackend t1 ~ SqlBackend, PersistEntityBackend t2 ~ SqlBackend, SpockConn m ~ SqlBackend, typ2 ~ Key b, typ1 ~ Key a, HasSpock m, SqlSelect (SqlExpr (Entity t2)) (Entity b)) => typ1 -> EntityField t1 typ1 -> EntityField t1 typ2 -> EntityField t2 typ2 -> m [Entity b]
manyThrough key joinId1 joinId2 targetId= runSQL $ select $
from $ \(joinTable `InnerJoin` target) -> do
on (joinTable ^. joinId2 ==. target ^. targetId)
where_ (joinTable ^. joinId1 ==. val (key))
return (target)