First of all I'm a Haskell noob so if there is something obvious I'm doing wrong sorry.
Say I have something like:
Object1
stuff Text
other Object2Id Maybe
Object2
otherStuff Text
in my config/models file
How do I get the Object2 associated to Object1 if I have a handler of type:
getObject1R :: Object1Id -> Handler Html
If I understand correctly I want to go from a Maybe Object2Id to a Maybe Object with calling runDB $ get in the middle and with everything I've tried the types don't line up properly.
Thanks
EDIT
here is one attempt that I feel is close but isn't correct:
object1 <- runDB $ get object1Id
maybeObj2 <- case (object1Other object1) of
Just obj2Id -> Just $ runDB $ get obj2Id
Nothing -> Nothing
EDIT 2 (figured it out)
So I figured out how to get it to compile and work! Thanks for the comments and help.
here is my code (in the handler):
(object1, maybeObject2) <- runDB $ do
object1 <- get404 object1Id
maybeObject2 <- case (object1Other object1) of
Just object2Id -> get object2Id
Nothing -> return Nothing
return (object1,maybeObject2)
I'm sure there are better/faster/easier ways to do it, but this worked.
So I figured out how to get it to compile and work! Thanks for the comments and help.
here is my code (in the handler):
(object1, maybeObject2) <- runDB $ do
object1 <- get404 object1Id
maybeObject2 <- case (object1Other object1) of
Just object2Id -> get object2Id
Nothing -> return Nothing
return (object1,maybeObject2)
I'm sure there are better/faster/easier ways to do it, but this worked.
Related
I've looked for this, but the answer found here ends up on a list containing the value. I'm wondering if there isn't another, more straightforward way to do what I need.
I have a form:
formReview :: UserId -> Form Review
formReview uid = renderDivs $ Review <$>
areq textField "Text" Nothing <*>
areq intField "Rating" Nothing <*>
areq (selectField films) "Film" Nothing <*>
pure uid
as you can see I'm trying to pass an user ID to the form, because these are the fields for Review:
Review
text Text
rating Int
film FilmId
author UserId
it requires the ID of the author.
The way I'm trying to do this is by doing the following on postReviewsR:
postReviewsR :: Handler Html
postReviewsR = do
uid <- lookupSession "_ID"
case uid of
Nothing -> do
defaultLayout [whamlet| <h1> User isn't logged in.|]
Just uid ->
((result, _), _) <- runFormPost $ formReview uid
case result of
FormSuccess review -> do
runDB $ insert review
defaultLayout [whamlet|
<h1> Review posted.
|]
_ -> redirect ReviewsR
it has to be a Maybe because in theory you could try to post something without being logged in, so uid could be empty. If I try to go straight to ((result, _), _) <- runFormPost $ formReview uid it says there's a Maybe Text.
Now my problem is similar the other post, it's this error:
• Couldn't match type ‘Text’ with ‘Key User’
Expected type: UserId
Actual type: Text
• In the first argument of ‘formReview’, namely ‘uid’
In the second argument of ‘($)’, namely ‘formReview uid’
In a stmt of a 'do' block:
((result, _), _) <- runFormPost $ formReview uid
and the suggestion in the linked post is to use keyToValues :: Key record -> [PersistValue] to turn the Key, which apparently is just Text, into what I need, the UserId.
It seems too clunky to me that I would need to do all this, then use head on that list to get the value within, all to get the ID into the form. There has to be another, more correct way, right? What am I missing here? How do I get the ID of the user who's making the Review in there?
EDIT: I should point out that I'm not using Yesod.Auth so I can't use its functions.
Assuming you're using a SQL backend, check out the persistent SQL documentation. You'll find the toSqlKey :: ToBackendKey SqlBackend record => Int64 -> Key record function. Basically, you'll need to parse your Text to Int64 and get a key using toSqlKey. You should probably also check if the key you're getting is actually valid.
(You've apparently misread the error, your uid is just a Text value, but you need a UserID which is a Key User).
For my previous question on chaining failures, Michael Snoyman had suggested I use MaybeT to run them so if any of them fails, it will just short-circuit to Nothing.
I was under the impression runDb runs everything in a transaction. So shouldn't a failure at any point in code automatically rollback the transaction?
mauth <- runDb $ runMaybeT $ do
valid <- MaybeT $ return $ listToMaybe errs
uid <- MaybeT $ insertUnique u
vid <- MaybeT $ getBy $ UniqueField v -- this step fails but previous insert does not roll back
auth <- liftIO $ createAuthToken uid
return auth
When I run the above code, the getBy fails but user was still inserted. Am I misunderstanding that runDb will rollback on a Nothing inside MaybeT? Do I need to use some other Monad for this to work?
Appreciate your thoughts on how to best rollback on failure.
Update:
This is what I ended up doing per Michael's suggestion.
mauth <- runDb $ do
ma <- runMaybeT $ do
valid <- ...
case ma of
Just _ -> return ma
Nothing -> liftIO $ throwIO MyException
Now I need to figure out how to catch this exception nicely outside and return a proper error message back.
Thanks!
Returning Nothing is not the same thing as a failure. You'd need to throw a runtime exception (via something like throwIO) for Persistent to treat it as a rollback situation.
I am trying to figure out if there is a way to avoid lots of case statements while inserting records into the DB.
My current code sort of looks like this:
mt1 <- runDb $ do
muid <- insertUnique user
case muid of
Just uid -> do
let t1 = Table1 {..., user = uid}
maid <- insertUnique t1
case maid of
Just aid -> do
mo <- getBy $ UniqueField "somevalue"
case mo of
Just (Entity oid o) -> do
mcid <- insertUnique Table2 {..., oid = oid}
case mcid of
Just cid -> do
mfid <- insertUnique Table3 {..., cid = cid}
case mfid of
Just fid -> Just t1 -- original t1 that was created at the top of the chain
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
Nothing -> Nothing
First of all, I have issues getting the code to compile but instead of trying to debug that, I wanted to see if there is a better way to do this.
At a conceptual level, I want to do something like below, where all the Maybe values get unwrapped automatically to be used in subsequent invocations. If any point, we hit a Nothing, I just want to return Nothing. The whole code will run in a single transaction so if we hit a Nothing in between, the transaction is rolled back
runDb $ do
uid <- insertUnique user
let t1 = Table1 {..., user = uid} -- uid is unwrapped to just the value
aid <- insertUnique t1
o <- getBy $ UniqueField "somevalue"
cid <- insertUnique Table2 {..., oid = oid}
fid <- insertUnique Table3 {..., cid = cid}
Just t1
I am Haskell beginner so I only have superficial understanding of Monads (I can use the simple ones fine) but when it comes to use it inside something like runDb of Persistent, I have no idea how to put the pieces together.
Any suggestions on how I can simply the logic so I don't end up checking for failure each step of the way?
Update: Based on Michael's answer, I did something like this and it automatically unwraps the maybes when used.
mt1 <- runDb $ runMaybeT $ do
uid <- MaybeT $ insertUnique user
...
case mt1 of
Just t -> return t
Nothing -> lift $ left ...
Thanks!
The standard approach to something like this is the MaybeT monad transformer. Something like the following will probably work:
runMaybeT $ do
uid <- MaybeT $ insertUnique user
...
I try to put a session value in a variable to display it in my .hamlet but it does not focntion!
getEtatR :: Handler Html
getEtatR = do
mSessionValue <- lookupSession "myKey"
let myValue = mSessionValue :: Maybe Text
defaultLayout $ do
aDomId <- newIdent
setTitle "mon titre"
$(widgetFile "etatWidget")
I need #{myValue} to put it in my etat.hamlet
The problem is the type of myValue, which is Maybe Text. In order for a variable to show up in the template, it has to be an instance of Text.Blaze.ToMarkup.... So Text, String, or Int would all work, but "Maybe a" does not.
There are many ways to convert a "Maybe Text" to a ToMarkup. If you know for sure that the Maybe will not be a "Nothing", just strip the maybe using fromJust (imported from Data.Maybe).... But beware that if it ever does come up as a Nothing the program will crash. Similarly you could use a case statement to fill in the Nothing case, like this
myVariable = case mSessionValue of
Just x -> x
Nothing -> "<No session value>"
You can also do a quick check by converting mSessionValue to a string using show.
The following works for me....
getEtatR :: Handler Html
getEtatR = do
mSessionValue <- lookupSession "myKey"
let myValue = show mSessionValue
defaultLayout $ do
aDomId <- newIdent
setTitle "mon titre"
$(widgetFile "etatWidget")
using etatWidget.hamlet
<h1>#{myValue}
If all you want is to display the value and get it out of Maybe, you can do this directly inside the hamlet
$maybe val <- mSessionValue
<p>#{val}
$nothing
<p>No Value Set
I have searched in internet, in the Yesod Web ebook and other tutorials (Yesod Tutorial) but I have not been able to clarify this problem. I am using the scaffolded site.
I have a handler, inside it returns a value, the email if the user is authenticated or a string if he is not. What I want is to return the localized message instead the string "(Unknown User ID)". My problem is to use a value from the message file (ex. MsgHello), if I do this, it returns errors like:
Couldn't match expected type AppMessage' with actual typeText'
I have tried using (show MsgHello) or (pack MsgHello), even calling msg <- getMessageRender but I have not been able to do what I expect. If you have any suggestions, they are welcome.
Thanks!!
PD: This is part of the code that I am working on, line :
getUserProfileR :: Handler RepHtml
getUserProfileR = do
maid <- maybeAuth
let user = case maid of
Nothing -> "(Unknown User ID)"
Just (Entity _ u) -> userEmail u
defaultLayout $ do
setTitleI MsgUserProfile
$(widgetFile "nhUserProfile")
Thanks to Tickhon Jelvis for pointing out those web pages, also I found this one: Poly Hamlet i18n where I was able to get the solution to the problem.
So, if I would like to use a localized message, I would do:
getUserProfileR :: Handler RepHtml
getUserProfileR = do
maid <- maybeAuth
msg <- getRenderMessage
let user = case maid of
Nothing -> msg MsgNoUser --"(Unknown User ID)"
Just (Entity _ u) -> userEmail u
defaultLayout $ do
setTitleI MsgUserProfile
$(widgetFile "nhUserProfile")
Also remember that there is a helper function "setTitleI" which takes directly a Msg value and avoids the use of "msg MsgThisPageTitle"
My understanding of the I18N module is that you want to take your AppMessage value and use renderMessage on it.
You need to pass in a type specifying your translation type and a list of languages as well as your message. The translation type is created using the mkMessage function and the list of languages looks something like ["en-US", "en-GB", "fr"].