Extracting database field values inside a Handler - haskell

I would like to extract a database field (Text) and pass it as an argument to another function from a Handler. However, I run into Type errors. Completely made up example so may feel a bit contrived but should illustrate the issue I am having.
Person
name Text
Car
personId PersonId
name Text
type Text
I would like to get a Car entity and then find the corresponding Person. Get his name and then pass it as an argument. Something like:
data MyD = MyD { input1 :: Int}
entryForm :: Text -> Form MyD -- Update: Removed the incorrect extra parameter
entryForm n1 = renderDivs $ MyD
<$> areq intField n1 Nothing
My get handler looks like:
getInputR :: CarId -> Handler Html
getInputR carId = do
car <- runDB $ get404 carId
pid <- carPersonId car
name <- getPName pid
(widget, enctype) <- generateFormPost $ entryForm name
defaultLayout $ do
$(widgetFile "my_template")
where
getPName pid = do
person <- runDB $ get404 pid
personName person
I get an error saying:
Couldn't match expected type `HandlerT App IO t0'
with actual type `KeyBackend
persistent-1.2.1:Database.Persist.Sql.Types.SqlBackend Person'
In the return type of a call of `carPersonId'
In a stmt of a 'do' block: pid <- carPersonId car
What am I doing wrong?
Thanks!

Try changing
pid <- carPersonId car
name <- getPName pid
to
name <- getPName $ carPersonId car
The value returned from your runDB call is not inside the handler monad so you don't need to use the arrow syntax to access it.
For the second error, the issue is similar: The getPName function's return type is in the Handler monad since it uses runDB, so you need to use return to put the value into the monad:
getPName pid = do
person <- runDB $ get404 pid
return $ personName person

Related

Couldn't match expected type 'SomeEntity’ with actual type ‘Key SomeEntity’

Hi I am a beginner in Haskell and Yesod and I need help with this problem.
These are my entities:
Location
name Text sqltype=varchar(255)
address AddressId Maybe sqltype=varchar(255)
UniqueLocationName name
deriving Show Typeable
Manifestation
name Text sqltype=varchar(255)
description Text Maybe sqltype=varchar(255)
category Category Maybe
startDateTime UTCTime sqltype=DateTime
location LocationId Maybe sqltype=varchar(255)
UniqueManName name
deriving Show Typeable
This is my handler that calls the man-details template:
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(_, user) <- requireAuthPair
md <- runDB $ get404 mid -- type is Manifestation
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")
And part of the man-details hamlet file where I want to display information about the event:
<div class="row">
<div class="col-md-5">
<div class="project-info-box mt-0">
<h2>#{manifestationName md}
<p class="mb-0">#{fromJust(manifestationDescription md)}
<div class="project-info-box">
<p><b>Start :</b>{show $ manifestationStartDateTime md}
<p><b>Location :</b>#{locationName (manifestationLocation md)}
In this case error is :
Couldn't match expected type ‘Location’
with actual type ‘Maybe (Key Location)’
Then I try like this :
$maybe l <- manifestationLocation md
<p><b>Location :</b>{locationName l}
And error is :
Couldn't match expected type ‘Location’
with actual type ‘Key Location’
I apologize for the huge question, but I don't know how to get out of this, ie how to get only the value out of this pair (Key Location)?
Any advice and help is welcome,
Thanks.
I'll post my solution, it's a beginner's problem, but it might be useful to someone.
As #Willem Van Onsem wrote in the comment, I again made a query to the database
now for a location.
Now handler looks like this, with the fromJust function because of the Maybe wrapper:
getManDetailsR :: ManifestationId -> Handler Html
getManDetailsR mid = do
(_, user) <- requireAuthPair
md <- runDB $ get404 mid
loc <- runDB $ get404 $ fromJust(manifestationLocation md) -- and now use loc in template
liftIO $ print (loc)
defaultLayout $ do
setTitle "Manifestation details"
$(widgetFile "man-details")

Yesod - Error "couldn't match expected type" when extracting data from database

Having the following database models, I am trying to get the city name (a certain field from city - a related entity) of an event :
City json
name Text
countryId CountryId
UniqueCity name
deriving Eq
deriving Show
Event
title Text
description Text
date UTCTime
cityId CityId
extractCityName :: EventId -> Text
extractCityName eventId = do
event <- runDB $ get404 eventId
city <- runDB $ get404 (eventCityId event)
x <- cityName city
return cityName
And I am having this error even if the function is not called yet(at compile time):
Couldn't match expected type `HandlerT site0 IO t0' with actual type `Text'
In a stmt of a 'do' block: x <- cityName city
In the expression:
do { event <- runDB $ get404 eventId;
city <- runDB $ get404 (eventCityId event);
x <- cityName city;
return cityName }
Can you please help me figure out what is wrong with my code?
That's because the type signature of runDB is YesodDB site a -> HandlerT site IO a. Unless you want to run database related actions in yesod handers, you won't be needing runDB. The code for extracting city name would be like this:
extractCityName :: EventId -> ReaderT SqlBackend IO (Maybe Text)
extractCityName eventId = do
event <- selectFirst [EventId ==. eventId] [LimitTo 1]
case event of
Nothing -> return Nothing
Just event' -> do
city <- getJust (eventCityId $ entityVal event')
return $ Just $ cityName city
Now you can use the above function in the Yesod handlers when you actually need it. You can do the pattern matching in the result and send a 404 page if the result is Nothing.

convert PersistValue to Text in Yesod

I want get the attribute value from Persist Entity; so i have the following code
userToText userId = do
user <- runDB $ get404 userId
userName user
This code doesn't compile, so I wrote those alternative version
userToText userId = do
user <- runDB $ get404 userId
listToJSON [user]
userToText userId = do
(_,_,_,_,_,name,_,_) <- runDB $ get404 userId
show name
All generate the same error
Handler/Report.hs:105:9:
Couldn't match expected type ‘HandlerT site IO b’
with actual type ‘Text’
Relevant bindings include
userToText :: Key PersistValue -> HandlerT site IO b
(bound at Handler/Report.hs:102:1)
In a stmt of a 'do' block: listToJSON [user]
In the expression:
do { user <- runDB $ get404 userId;
listToJSON [user] }
thanks for the help
Your code is in the Handler monad, so your function needs to return something of the type Handler Text rather than just Text:
userToText :: UserId -> Handler Text
userToText userId = do
user <- runDB $ get404 userId
return $ userName user -- Note the `return` here
(This is similar to how functions like getLine have type IO String rather than String).

Yesod - Extract session data (non-String), store it and use it

Hi there.
Here is the code I'm trying to make work :
getGameR :: Handler Html
getGameR = do
sess <- getSession
defaultLayout $ do
setTitle "Game"
$(widgetFile "hamletFile")
where
person = read $ fromJust $ Map.lookup "person" sess :: Person
data Person = Person
{
name :: Text
}
deriving (Show, Read)
The error is the following:
Handler/MyHandler.hs:87:56: Not in scope: `sess'
What I'm trying to do, is to extract data from Yesod Session (data of type Person) and store it inside 'person', to be able to use it inside the hamlet file.
Is there a way to get around that error?
If it's not possible, can you suggest another way around?
Thanks in advance.
sess is local to the do block, and thus it is not in scope in the person definition. As far as that error goes, using let inside the do block should be enough:
getGameR :: Handler Html
getGameR = do
sess <- getSession
let person = read $ fromJust $ Map.lookup "person" sess :: Person
defaultLayout $ do
setTitle "Game"
$(widgetFile "hamletFile")
If you just want to lookup single value, consider using lookupSession instead. Also, fromJust throws exception if key is not in session, you might use fromMaybe:
getGameR :: Handler Html
getGameR = do
mbPersonName <- lookupSession "person"
let defaultPerson = Person "anonymous"
let person = fromMaybe defaultPerson (readMaybe .unpack =<< mbPersonName) :: Person
defaultLayout $ do
setTitle "Game"
$(widgetFile "hamletFile")
Here is my helpers for dealing with session:
import Text.Read (readMaybe)
import Data.Maybe (fromMaybe)
readSession :: Read a => Text -> Handler (Maybe a)
readSession name = do
textValue <- lookupSession name
return (readMaybe . unpack =<< textValue)
readSessionDef :: Read a => a -> Text -> Handler a
readSessionDef def name = do
mbValue <- readSession name
return $ fromMaybe def mbValue
readSession reads anything that can be readden and returns a Maybe. readSessionDef returns default value if such key is not present in session:
getGameR :: Handler Html
getGameR = do
person <- readSessionDef (Person "anonymous") "person"
defaultLayout $ do
setTitle "Game"
$(widgetFile "hamletFile")

Capturing Persistent Relations in a Form

I have defined a one-to-many relationship in Persistent but could not figure out how to create a form that can take one of the foreign keys as input. Simplifying my use case to something like this:
Person
name String
Car
personId PersonId
name Text
type Text
Now when I try to generate a Form for Car, what should be the field type for personId? I tried something like this but get an error:
entryForm :: Maybe Car -> Form Car
entryForm car = renderDivs $ Car
<$> areq personIdField "Person" Nothing
<*> areq textField "Car Name" ( carName <$> car)
<*> areq textField "Type" ( carType <$> car)
When I run the above I get the error: Not in scope: `personIdField'.
I tried intField and it says:
Couldn't match expected type `KeyBackend
persistent-1.2.1:Database.Persist.Sql.Types.SqlBackend Person'
with actual type `Text'
Expected type: Field
m0
(KeyBackend
persistent-1.2.1:Database.Persist.Sql.Types.SqlBackend Person)
Actual type: Field m0 Text
In the first argument of `areq', namely `intField'
In the second argument of `(<$>)', namely
`areq intField "Person" Nothing
Ideally I would like to populate a drop down of Person Names (if there are not too many) or have a free form text field (e.g., with autocomplete) when there are too many. Any suggestions on how I can get foreign key as an input from the user?
Update:
I tried using selectField as follows but not sure if I am doing this correctly. I still get an error. First I created a where statement to get personId:
where
personId = do
person <- runDB $ selectFirst [] [Asc PersonName]
case person of
Just (Entity pId p) -> return pId
-- Nothing -> ???
and then I changed my first areq to
<$> areq (selectField [("First", personId)]) "Person Name" Nothing
Thanks!
I was able to figure out how to use selectField properly. This is what I ended up doing:
where
people = do
entities <- runDB $ selectList [] [Asc PersonName]
optionsPairs $ map (\p -> (personName $ entityVal p, entityKey p)) entities
The form field became:
<$> areq (selectField people) "Person Name" Nothing
I still have not figured out free form entry just yet but this is a good start.

Resources