How to create a entity key form field - haskell

I want to create a new form field to enter an entity key, in this case an ArticleId instead of an Integer field, can I do that?
There is my Article model
Article
title Text
content Text
userId Int
score Int Maybe
deriving Show
deriving Eq
And there is my AForm Article handler
<$> areq intField (bfs ("Article Id" :: Text)) Nothing
<*> areq intField (bfs ("Tag Id" :: Text)) Nothing
I want to do this to avoid use toSqlKey in my selectList
Thanks!

Related

Use one form field for two Model entries in Yesod

I am making a link aggregator where people can submit articles.
My data model contains
Article
title Text
url Text
domain Text
I would like the user to input a url into the form and then I run a function on the URL to extract the domain and then save both in the database.
I would like the user to only have to enter the URL once, as in:
entryForm = renderDivs $ Article¬
<$> areq textField "Url" Nothing¬
<*> areq textField "Title" Nothing¬
But I get this error
Couldn't match type ‘Text -> Article’ with ‘Article’
Expected type: Form Article
Actual type: blaze-markup-0.7.0.0:Text.Blaze.Internal.Markup
-> MForm
(HandlerT App IO)
(FormResult (Text -> Article),
WidgetT (HandlerSite (HandlerT App IO)) IO ())
In the expression:
renderDivs
$ Article <$> areq textField "Url" Nothing
<*> areq textField "Title" Nothing
In an equation for ‘entryForm’:
entryForm
= renderDivs
$ Article <$> areq textField "Url" Nothing
<*> areq textField "Title" Nothing
because clearly the form doesn't match the type Article.
I am not sure how to proceed. I've been told I can either a)write an alternative Article' datatype and convert between the two, or b) create my own custom field, though both of those seem difficult to me as a newbie.
I'd recommend a helper function like:
makeArticle :: Text -> Text -> Article
Which takes the title and URL, extracts the domain name from the URL, and constructs an Article value. Then you can use that in place of calling the Article data constructor directly.

complex record form in yesod

I am trying to make a form for the following Configuration record :
data Configuration = Configuration
{ cMail :: Mail
, cIdentity :: Identity
, cAttachments :: [(Text, FilePath)]
} deriving Eq
data Mail = Mail
{ mFullMail :: Text
, mServer :: Text
, mPort :: Text
, mUser :: Text
, mPassword :: Text
} deriving (Eq, Show)
data Identity = Identity
{ iName :: Text
, iLastName :: Text
, iHonorific :: Maybe Text
, iBirthday :: Maybe Text
, iOrigin :: Maybe Text
, iNationality :: Maybe Text
, iAddress :: Maybe Text
, iTown :: Maybe Text
, iCp :: Maybe Text
, iCountry :: Maybe Text
, iPhone :: Maybe Text
, iMobile :: Maybe Text
} deriving (Eq, Show)
I have figured the following which I have yet to test :
configureForm :: Form Configuration
configureForm = renderDivs $ Configuration
-- I don't know if the following is right
<$> $ Mail <$> areq textField "fullmail :" Nothing
<*> areq textField "server : " Nothing
-- etc..
<*> $ Identity <$> areq textField "name : " Nothing
<*> areq textField "lastname : " Nothing
<*> aopt textField "honorific : " Nothing
-- etc..
<*> -- the part I can't figure out
But am stuck with the last part so can't test.
Any tips as how to complete the form?

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.

How to define a field on an applicative form for a foreign key in Yesod?

If we have defined 2 simple objects in our models file, for example :-
Person
name Text
Age Int
Book
title Text
author Text
We can define an applicative form for Book as :-
addBookForm = renderDivs $ Book
<$> areq textField "title" Nothing
<*> areq textField "author" Nothing
However, if we want to change the author from just a text field, to the id of a person, as :-
Book
title Text
author PersonId
Then the above form won't compile, with this error :-
Couldn't match expected type `KeyBackend Database.Persist.GenericSql.Raw.SqlBackend Person' with actual type `Text'
Expected type: Field
sub0
master0
(KeyBackend Database.Persist.GenericSql.Raw.SqlBackend Person)
Actual type: Field sub0 master0 Text
In the first argument of `areq', namely `textField'
In the second argument of `(<*>)', namely
`areq textField "author" Nothing'
How do we now define the author field ?
Do we need to use a monadic form ?
Thanks !
The error message means you are trying to use Text (from the field result) as a Key.
You can use checkMMap to wrap the textField and modify the result:
addBookForm = renderDivs $ Book
<$> areq textField "title" Nothing
<*> (entityKey <$> areq authorField "author" Nothing)
where
authorField = checkMMap findAuthor (personName . entityVal) textField
findAuthor name = do
mperson <- runDB $ selectFirst [PersonName ==. name] []
case mperson of
Just person -> return $ Right person
Nothing -> return $ Left ("Person not found." :: Text)
The findAuthor function gets simpler if you add a unique constructor to the Person field:
Person
name Text
...
UniquePerson name
Then instead of selectFirst ... you can do
mperson <- runDB $ getBy $ UniquePerson name

Forms for nested structures in yesod

I have the following types:
data Cheese = Cheddar Int | Edam String String | Cottage String Int
data Meal = Meal {
nameOfMeal :: String,
... other generic fields
cheese :: Cheese
}
Currently my forms look like:
cheddarForm = renderTable $ construct
<$> areq textField "Name of meal" Nothing
<*> areq intField "Cheddar fat" Nothing
where
construct name fat = Meal name (Cheddar fat)
I am currently quite happy with the fact, that I need one form for every type of 'cheese' (although I wouldn't certainly mind having a dynamic form..). However, I would really like to get rid of repeating the 'Name of meal' in every form. Can I somehow combine the forms, or do I have to ultimately go for Monadic forms?
Couldn't you just pass around
conWithNOM ctr = ctr
<$> areq textField "Name of meal" Nothing
and call that against your other form fields?
cheddarForm = renderTable $ conWithNOM construct
<*> areq intField "Cheddar fat" Nothing
where
construct name fat = Meal name (Cheddar fat)

Resources