I think I did asked a similar question some time ago but it was not answered due to unstable API. So I was waiting for the 0.13 to pass by. I am not sure if it is correct to bring up a similar question...?
What is the alternative to interpreted runChildrenWith(Text) and mapSplices in the compiled splices world? (this combination seems to be the most common)
I would really appreciate some code examples if possible.
If I understand correctly, we get together all the application splices and then add them to the heistInit. Can anyone show how to do it please?
Does the splice binding tag has to be unique across the whole application?
Is there a completed snap project utilising new APIs and compiled splices so that I could read and see learn?
Thank you.
-- UPDATE --
Great answer below. But some parts (the ones with lenses) got me even more confused, unfortunately.
If I understand correctly this is the simple way to splice a string:
mySplice = "testSplice" ## testSplice
where testSplice = return $ C.yieldRuntimeText $ do
return "text to be spliced"
If i need to run the spliced string several times, say in 5 table raws i would do it like this:
mySplices = C.manyWithSplices C.runChildren mySplice
Is this correct?
I get bunch of errors trying to add the splices in heist config.
addConfig h $ mempty
{
hcCompiledSplices = "mySplice" ## mySplice -- or mySplices
}
Where am I going wrong? Sorry for being slow.
All I need really ( just for now so I can understand) is to splice and display a simple string that I receive from database.
-- UPDATE 2 --
Thanks to the extremle helpfull Daniel`s answer I can finally get something working.
So far I get both variants of code working.
The first one, thanks to Daniel
stringSplice :: Monad n => C.Splice n
stringSplice = C.manyWithSplices C.runChildren splicefuncs (return ["aa","bb","cc"])
where
splicefuncs = "string" ## (C.pureSplice . C.textSplice $ id)
And the secod
testSplice :: C.Splice (Handler App App)
testSplice = return $ C.yieldRuntimeText $ return "text to be spliced"
Where
(C.pureSplice . C.textSplice $ id)
produces similar results to
return $ C.yieldRuntimeText $ return "text to be spliced"
Is there difference between the above? Any cases that one would prefer one to another? They seem to produce the same results.
There is a "deferMany" function in the compiled splices lib that, according to the docs, produces similar results to the mapSplices in interpreted lib.
Can we use it instead of "C.manyWithSplices C.runChildren" combination??
Let's say you want to display information about a list of persons using compiled splices (assume that we start from the scaffolding generated by snap init.)
A very simple _persons.tpl template with dummy values would be something like
<body>
<person>
<div>
<h1><name>dummy name</name></h1>
<p><age>77</age></p>
<p><location>jauja</location></p>
</div>
</person>
</body>
Where person, name, age, and location are the tags to be spliced.
We define a trivial Snaplet that holds the info
data Foo = Foo
{
_persons :: [Person]
}
makeLenses ''Foo
data Person = Person
{
_name :: Text
, _age :: Int
, _location :: Text
}
makeLenses ''Person
and we add it to the App record:
data App = App
{ _heist :: Snaplet (Heist App)
, _sess :: Snaplet SessionManager
, _auth :: Snaplet (AuthManager App)
, _foo :: Snaplet Foo
}
we add the following to the app initializer
f <- nestSnaplet "foo" foo $ makeSnaplet "foo" "Foo Snaplet" Nothing $ return $ Foo $
[ Person "Ricardo" 33 "Los Cantones"
, Person "Luis" 38 "Montealto"
]
...
return $ App h s a f
This function constructs a Handler that returns the list of persons (using view from Control.Lens):
personH :: SnapletLens b Foo -> Handler b b [Person]
personH l = withTop l $ view persons <$> get
This function constructs the appropiate compiled splice from a RuntimeSplice that produces a list of Persons. RuntimeSplices represent information that can only be known at run time, as opposed to load time:
personSplice :: Monad n => RuntimeSplice n [Person] -> C.Splice n
personSplice = C.manyWithSplices C.runChildren splicefuncs
where
splicefuncs = mconcat
[ "name" ## (C.pureSplice . C.textSplice $ view name)
, "age" ## (C.pureSplice . C.textSplice $ T.pack . show . view age)
, "location" ## (C.pureSplice . C.textSplice $ view location)
]
And this function can be used to register the splice in the global Heist configuration. Notice that we lift the Handler into a RuntimeSplice:
addPersonSplices :: HasHeist b => Snaplet (Heist b) ->
SnapletLens b Foo ->
Initializer b v ()
addPersonSplices h l = addConfig h $ mempty
{
hcCompiledSplices = "person" ## (personSplice . lift $ personH l)
}
Be sure to add this line to the app initializer:
addPersonSplices h foo
And to add the following pair to the app's routes:
("/persons", cRender "_persons")
If you now run the server, navigating to http://127.0.0.1:8000/persons should show the list.
UPDATE
For the simpler case (no complex records, no lenses) in which you only want to show a list of strings.
The template could be something like:
<body>
<strings>
<p><string>dummy value</string></p>
</strings>
</body>
The top-level splice would be:
stringSplice :: Monad n => C.Splice n
stringSplice = C.manyWithSplices C.runChildren splicefuncs (return ["aa","bb","cc"])
where
splicefuncs = "string" ## (C.pureSplice . C.textSplice $ id)
This means "when we encounter the tag associated to this splice, perform an action that produces a list of strings, and for each string, render the contents of the tag, substituting the current string for the string tag".
Notice that the signature of manyWithSplices forces the stuff to the right of the (##) to have type RuntimeSplice n Text -> Splice n. Here id has type Text -> Text. C.TextSplice transforms it into something of type Text -> Builder, and C.pureSplice performs the final transformation into a RuntimeSplice n Text -> Splice n.
In place of (return ["aa","bb","cc"]) you could provide a more complex action that connected a database and extracted the strings form there.
A function to register this splice would be:
addStringSplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
addStringSplices h = addConfig h $ mempty
{
hcCompiledSplices = "strings" ## stringSplice
}
Related
Starting out with Haskell and Yesod, probably getting a bit too far with Yesod relative to Haskell :)
I build entities using Persistent via
share [mkPersist sqlSettings, mkMigrate "migrateAll"][persistLowerCase|
Game
title String
company String
UniqueTitle title
deriving Show
Tag
label String
description String Maybe
UniqueLabel label
deriving Show
GameTag
gameId GameId
tagId TagId
UniqueGameTag gameId tagId
|]
-- Yesod related code ...
In main I have
main :: IO ()
main = do
let taggings = fromFile :: [(Game, Tag)] -- fromFile code not included
runStderrLoggingT $ withSqlitePool ":inmemory:" 10 $ λpool → liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
let (g, t) = head taggings
gid ← insert g
tid ← insert t
insert (GameTag gid tid)
warp 3000 $ App pool
Doing this I get the first relation into the database, and by selecting elements from the list I can add more 'by hand', but I can't figure out how to get all the relations into the database by somehow iterating over taggings. How do i define a function that I can map over taggings ::[(Game, Tag)] and inserts the game tags of the type GameTag constructed
by Persistent?
The main trick here isn't in pulling out the function, that is easy:
f (g, t) = do
gid <- insert g
tid <- insert t
insert (GameTag gid tid)
The trick is knowing how to use this.... Standard map won't work alone, because the function is defined in a monad (you can use it, it will just give you a list of actions back without running them).
map f taggings -- returns just a list, type [ResourceT IO a], doesn't run anything
Here are two ways to actually run the actions from within main.
sequence (map f taggings) --sequentially runs the actions in the list
or, the more readable
forM taggings f
or, in the slightly more verbose
forM taggings $ \tagging -> do
f tagging
You also might want to look at mapM. Also also should learn about forM_ and sequence_ to supress the (often useless) return values.
I am having issues with types matching up in persistent. I have a module called Storage.Mongo like so:
type FieldMap = Map.Map T.Text T.Text
let mongoSettings = (mkPersistSettings (ConT ''MongoBackend)) {mpsGeneric = False}
in share [mkPersist mongoSettings] [persistLowerCase|
Notice
rawData FieldMap
deriving Show
|]
-- | This is the default database pool
defaultPool = createMongoDBPool
"system_of_record"
"localhost"
(PortNumber 33107)
(Just (MongoAuth "reader" "password"))
10
10
30
-- | Save notice to database
saveNotices x = do pool <- defaultPool
runMongoDBPoolDef save pool
where save = mapM_ (insert.Notice) x
I am trying to pass the saveNotices command a field map, which it will convert to a Notice entity and save to a MongoDb database. To wit:
main = do files <- getArgs
mapM_ parseNotice files
where parseNotice f = do x <- parseFromFile fboFile f
case x of
Left err -> print err
Right notices -> mapM_ saveNotices notices
The parseNotice function returns a list of Maps:
notice = do noticeType <- openingTag
fields <- manyTill (try complexField <|> simpleField) (try closingTag)
return $ (Map.fromList(concat ([("NOTICETYPE", noticeType)]:fields)))
fboFile = many notice
I am not sure where the problem is. I believe I should let the compiler the know the type of
mapM_ (insert.Notice) x
is, but I am not sure what the type should be
This is the error I am getting
Couldn't match type 'PersistEntityBackend Notice' with 'MongoBackend' Expected type: PersistEntityBackend Notice Actual type: PersistMonadBackend (Action m) In the first argument of '(.)', namely 'insert' In the first argument of 'mapM_', namely '(insert . Notice)' In the expression: mapM_ (insert . Notice) x
It is failing trying to apply insert . Notice (space added for emphasis)
insert :: MonadIO' m => Collection -> Document -> Action m Value
type Collection = Text
It looks like insert expects a name as its first argument. The following might work.
mapM_ (insert "Notice" $ Notice) x
I am trying to create dynamic links using the Heist templating system. The problem is that the links are appearing as text rather than being interpreted as html. Is there a specific method to create dyamic lists like this with Heist?
The function where the link is constructed:
renderCategories :: Monad m => Db.Category -> I.Splice m
renderCategories (Db.Category catid catname catdesc) =
I.runChildrenWithText [ ("categoryId", T.concat $ ["<a href='http://localhost:8000/thread_home?cateid=", T.pack . show $ catid, "'>", T.pack . show $ catid, "</a>"])
, ("categoryName", catname)
, ("categoryDesc", catdesc)]
The tag appears as "http://localhost:8000/thread_home?cateid=1'>1" text on the webpage. And the source shows it as follows:
<a href='http://localhost:8000/thread_home?cateid=1'>1</a>
I figure that I need to have it print the actual < and > but I am not sure how to achieve this.
As I am currently running runChildrenWithText to populate this Heist template changing to just runChildrenWith requires splices instead of text and so instead of attempting this I am hoping there is some way to runChildrenWithText without the '<' and '>' being converted to '<' and '>'.
Any help is appreciated!
EDIT
I am trying to manually create the link using:
renderCategories :: Monad m => Db.Category -> I.Splice m
renderCategories (Db.Category catid catname catdesc) =
I.runChildrenWith [ ("categoryId", return $ X.Element "a"[("href", "http://localhost")] $ X.TextNode (T.pack $ show catid))]
However I am encountering two errors:
Couldn't match type `X.Node' with `[X.Node]'
Expected type: I.Splice m
Actual type: heist-0.11.1:Heist.Types.HeistT m m X.Node
In the expression:
return
$ X.Element "a" [("href", "http://localhost")]
$ X.TextNode (T.pack $ show catid)
and
Couldn't match expected type `[X.Node]' with actual type `X.Node'
In the return type of a call of `X.TextNode'
In the second argument of `($)', namely
`X.TextNode (T.pack $ show catid)'
I do not really understand these errors at the moment and any help is appreciated.
Working function for both returning the link and normal text:
renderCategories :: Monad m => Db.Category -> I.Splice m
renderCategories (Db.Category catid catname catdesc) =
I.runChildrenWith [( "categoryId", return $ [X.Element "a" [("href", T.concat $ ["http://localhost:8000/thread_home?cateid=", T.pack $ show catid] )] [X.TextNode (T.pack $ show catid)] ] )
, ("categoryName", I.textSplice catname)
, ("categoryDesc", I.textSplice catdesc)]
The behavior you are seeing is exactly what is intended. The reason you are having problems is because you're using runChildrenWithText which is a higher level function designed for situations where you are returning text nodes. It is meant for when you want that actual text on your page. What you are seeing is the correct way to achieve that.
A splice is a computation that returns a list of nodes.
type Splice n = HeistT n n [Node]
Node is a representation of the DOM as Haskell types, so if you want to return a link, you should do something like this:
return $ [Element "a" [("href", "http://localhost")] [TextNode (T.pack $ show catid)]]
To use this kind of a splice, you'll need to use runChildrenWith instead of runChildrenWithText.
If this manual creation of Nodes seems ugly to you, there's also a more convenient option. If you import the module Text.Blaze.Renderer.XmlHtml, you'll find functions there that let you generate Node trees using blaze-html syntax.
I'm new to Haskell, coming from Scala. I like Haskell, but I feel like I'm fighting the type system when it comes to using persistent.
My Request: I'd like to separate some insert logic into its own method. I can't quite figure out the types, or the right way to do this. All my failed attempts won't compile. More succinct questions below.
Here is the data declaration:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Curator
name String
url String
feed String
UniqueUrl url
deriving Show
Article
url String
title String
content String
curatorId CuratorId Eq
deriving Show
|]
Here's a failed attempt that doesn't work:
insertArticle :: String -> String -> String -> MaybeT (???)
insertArticle url title content = do
curatorId <- selectFirst [curatorName ==. "Fake Name"]
lift $ do
curator <- curatorId
insert (Article url title content curator)
So, my questions:
What type should be in ??? ?
Is lift in the right place? (usually the compiler is more helpful).
Is there a better way to do this?
PS - I have successfully abstracted other logic away, e.g. The insert is just causing me a world of pain. I was unable to get it to compile while using SqlPersistM
getFeeds :: SqlPersistM [Curator]
getFeeds = do
curatorIds <- selectList [] [Asc CuratorName]
let curatorGenerics = map entityVal curatorIds
let curators = map (\x -> x :: Curator) curatorGenerics
return curators
The return type of insertArticle should be SqlPersistM (Maybe ArticleId), because it returns Just an inserted article id or Nothing in SqlPersistM monad.
You can implement the function something like:
insertArticle :: String -> String -> String -> SqlPersistM (Maybe ArticleId)
insertArticle url title content = do
curatorEntity <- selectFirst [CuratorName ==. "Fake Name"] []
for curatorEntity $ \(Entity curatorId _) ->
insert (Article url title content curatorId)
I use for from Data.Traversable to handle the Maybe value selectFirst returns here.
But, actually, I don't like this type signature because it sticks to the sql backend. To make it more generalized, you can write a type annotation like this.
insertArticle :: (Applicative m, PersistQuery m, PersistMonadBackend m ~ PersistEntityBackend Curator) =>
String -> String -> String -> m (Maybe ArticleId)
The signature is a bit complex, but this function works with any backends.
By the way, your getFeeds can be simplified.
getFeeds :: (Functor m, PersistQuery m, PersistMonadBackend m ~ PersistEntityBackend Curator) =>
m [Curator]
getFeeds = map entityVal <$> selectList [] [Asc CuratorName]
I'm completely new to Yesod (and not very experienced in haskell) and I'm trying to build my first handler. I scraffolded my app using default parameters (I'm using Yesod 0.9.4.1 version and choose postgresql in scraffolding) and now I'm trying to retrieve some data from a table using selectList. I defined a new table (let's call it Foo) in models config file:
Foo
xStart Int
yStart Int
and want to pass a list of FooId's and some other Foo attributes so I defined a route:
/foos/#Int/#Int/*FooId FoosReturnR GET
and a handler:
module Handler.FoosReturn where
import Import
selectWindowSize :: Int
selectWindowSize = 10000
getFoosReturnR :: Int -> Int -> [FooId] -> Handler RepPlain
getFoosReturnR x y withoutIds = do
foos <- runDB $ selectList [FooId /<-. withoutIds,
FooXStart <. x + selectWindowSize,
FooXStart >=. x - selectWindowSize,
FooYStart <. y + selectWindowSize,
FooYStart >=. y - selectWindowSize] []
return $ RepPlain $ toContent $ show foos
I imported the handler in Application.hs and added it to cabal file and now when I'm trying to run it I receive an error saying that FooId is not an instance of MultiPiece - but when I try to make it an instance there is an error saying that FooId is a type synonym and cannot be an instance of MultiPiece - how to resolve this problem?
EDIT:
Daniel: well, actually I don't know what exactly is FooId - it's a part of Yesod's magic which I don't fully understand so far - it's generated automatically from the table definition - but it's a some kind of a number.
Because I don't know how to use MultiPiece I switched to simpler solution and modified:
route: /foos/#Int/#Int/#String FoosReturnR GET
handler: [added also some logging]
module Handler.FoosReturn where
import Import
import Data.List.Split
import qualified Data.Text.Lazy as TL
selectWindowSize :: Int
selectWindowSize = 10000
getFoosReturnR :: Int -> Int -> String -> Handler RepPlain
getFoosReturnR x y withoutIds = do
app <- getYesod
liftIO $ logLazyText (getLogger app) ("getFoosReturnR('" `TL.append` (TL.pack $ (show x) ++ "', '" ++ (show y) ++ "', '" ++ withoutIds ++ "') "))
foos <- runDB $ selectList [FooId /<-. (map (\a -> read a :: FooId) $ splitOn "," withoutIds),
FooXStart <. x + selectWindowSize,
FooXStart >=. x - selectWindowSize,
FooYStart <. y + selectWindowSize,
FooYStart >=. y - selectWindowSize] []
return $ RepPlain $ toContent $ show foos
and now it is compiling but when I browse to: http://localhost:3000/sectors/1/1/1,2 I get a page containing only:
Internal Server Error
Prelude.read: no parse
Well, I don't fully understand what is FooId here - how to create such a list of FooId's from list of strings containing numbers?
And of course a solution of how to make the FooId an instance of MultiPiece is most wanted.
EDIT:
Daniel and svachalek, thanks for your posts - I tried your (Daniel's) solution but then I was receiving errors saying that [FooId] is expected (as in the handler function declaration) but FooId type was given and this lead me to the following solution:
data FooIds = FooIds [FooId] deriving (Show, Read, Eq)
instance MultiPiece FooIds where
toMultiPiece (FooIds fooList) = map (Data.Text.pack . show) fooList
fromMultiPiece texts =
if length (filter isNothing listOfMaybeFooId) > 0
then Nothing
else Just $ FooIds $ map fromJust listOfMaybeFooId
where
listOfMaybeFooId = map constructMaybeFooId texts
constructMaybeFooId :: Text -> Maybe FooId
constructMaybeFooId x = case reads (Data.Text.unpack x) :: [(FooId,String)] of
[(foo,_)] -> Just foo
_ -> Nothing
of course I changed the route to: /foos/#Int/#Int/*FooIds FoosReturnR GET
and the handler to:
getFoosReturnR :: Int -> Int -> FooIds -> Handler RepPlain
getFoosReturnR coordX coordY (FooIds withoutIds) = do
and now I don't get any errors during compilation nor runtime, and the only not satisfying thing is that I always receive Not Found as a result, even if I supply parameters that should give me some results - so now I have to figure out how to determine what SQL was exactly sent to the database
EDIT:
Now I see that the "Not Found" is connected to the problem and that the above edit is not a solution - when I browse to localhost:3000/foos/4930000/3360000 then I get the results (but then the FooIds is empty) - but when I add something like: localhost:3000/sectors/4930000/3360000/1 then I always get "Not Found" - so it's still not working..
Wish I could help, but yesod has something to do with web applications, as far as I know, hence I've never really looked at it. So I can just try a stab in the air, maybe I hit something.
Hayoo leads to
class MultiPiece s where
fromMultiPiece :: [Text] -> Maybe s
toMultiPiece :: s -> [Text]
in Yesod.Dispatch. Since FooId seems to have a Read instance and probably a Show instance, you could try
{-# LANGUAGE TypeSynonymInstances #-}
-- maybe also FlexibleInstances
instance MultiPiece FooId where
toMultiPiece foo = [Text.pack $ show foo]
fromMultiPiece texts =
case reads (unpack $ Text.concat texts) :: [(FooId,String)] of
[(foo,_)] -> Just foo
_ -> Nothing
I have no idea whether that is close to the right thing, and I would have posted it as a comment, but it's too long and there's not much formatting in comments. If it doesn't help I will delete it to not give the impression your question already has an answer when it hasn't.
The problem is solved:)
You could either use my implementation from one of the last edits of the question and browse to URL like: http://localhost:3000/foos/4930000/3360000/Key {unKey = PersistInt64 3}/Key {unKey = PersistInt64 4}
The Key type derives Read but not in a very friendly (and expected) way:)
Or change the implementation of fromMultiPiece to:
instance MultiPiece FooIds where
toMultiPiece (FooIds fooList) = map (Data.Text.pack . show) fooList
fromMultiPiece texts =
if length (filter isNothing listOfMaybeFooId) > 0
then Nothing
else Just $ FooIds $ map fromJust listOfMaybeFooId
where
listOfMaybeFooId = map constructMaybeFooId texts
constructMaybeFooId :: Text -> Maybe FooId
constructMaybeFooId x = case TR.decimal x of
Left err -> Nothing
Right (el,_) -> Just $ Key (PersistInt64 el)
and use URLs like: http://localhost:3000/foos/4930000/3360000/1/2
Many thanks to David McBride from the Yesod Web Framework Google Group
EDIT: the above solution had only one disadvantage - using the PersistInt64 type - it's not a good practice to use such a details of implementation, but it can be repaired by using fromPersistValue and toPersistValue functions from Database.Persist as follows:
instance MultiPiece FooIds where
toMultiPiece (FooIds fooList) = map (persistValuetoText . unKey) fooList
where
persistValuetoText x = case fromPersistValue x of
Left _ -> Data.Text.pack ""
Right val -> Data.Text.pack $ show (val::Int)
fromMultiPiece texts =
if length (filter isNothing listOfMaybeFooId) > 0
then Nothing
else Just $ FooIds $ map fromJust listOfMaybeFooId
where
listOfMaybeFooId = map constructMaybeFooId texts
constructMaybeFooId :: Text -> Maybe FooId
constructMaybeFooId x = case TR.decimal x of
Left _ -> Nothing
Right (el,_) -> Just $ Key (toPersistValue (el :: Int))
Again, big thanks to David McBride also for this!
I'm also fairly new to Yesod and I gave in and added -XTypeSynonymInstances to the ghc-options in my .cabal file, and so far it's made life a lot easier for me. I'm not sure if it's the most elegant answer to this particular problem, but otherwise I predict you'll run into that instance-of-alias error pretty frequently. P.S. try id = (Key (PersistInt 64 n))