Skip to content

Commit

Permalink
Use record.field syntax instead of get #field record
Browse files Browse the repository at this point in the history
  • Loading branch information
mpscholten committed Sep 14, 2022
1 parent b91815e commit e7750f0
Show file tree
Hide file tree
Showing 23 changed files with 167 additions and 173 deletions.
22 changes: 11 additions & 11 deletions Guide/authentication.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ renderForm :: User -> Html
renderForm user = [hsx|
<form method="POST" action={CreateSessionAction}>
<div class="form-group">
<input name="email" value={get #email user} type="email" class="form-control" placeholder="E-Mail" required="required" autofocus="autofocus" />
<input name="email" value={user.email} type="email" class="form-control" placeholder="E-Mail" required="required" autofocus="autofocus" />
</div>
<div class="form-group">
<input name="password" type="password" class="form-control" placeholder="Password"/>
Expand Down Expand Up @@ -189,7 +189,7 @@ Inside your actions you can then use [`currentUser`](https://ihp.digitallyinduce

```haskell
action MyAction = do
let text = "Hello " <> (get #email currentUser)
let text = "Hello " <> currentUser.email
renderPlain text
```

Expand All @@ -201,18 +201,18 @@ You can use [`currentUserOrNothing`](https://ihp.digitallyinduced.com/api-docs/I
action MyAction = do
case currentUserOrNothing of
Just currentUser -> do
let text = "Hello " <> (get #email currentUser)
let text = "Hello " <> currentUser.email
renderPlain text
Nothing -> renderPlain "Please login first"
```

Additionally you can use [`currentUserId`](https://ihp.digitallyinduced.com/api-docs/IHP-LoginSupport-Helper-Controller.html#v:currentUserId) as a shortcut for `currentUser |> get #id`.
Additionally you can use [`currentUserId`](https://ihp.digitallyinduced.com/api-docs/IHP-LoginSupport-Helper-Controller.html#v:currentUserId) as a shortcut for `currentUser.id`.

You can also access the user using [`currentUser`](https://ihp.digitallyinduced.com/api-docs/IHP-LoginSupport-Helper-Controller.html#v:currentUser) inside your views:

```html
[hsx|
<h1>Hello {get #email currentUser}</h1>
<h1>Hello {currentUser.email}</h1>
|]
```

Expand All @@ -236,7 +236,7 @@ To block login (requires `isConfirmed` boolean field in `Users` table):
```haskell
instance Sessions.SessionsControllerConfig User where
beforeLogin user = do
unless (get #isConfirmed user) do
unless user.isConfirmed do
setErrorMessage "Please click the confirmation link we sent to your email before you can use IHP Cloud"
redirectTo NewSessionAction
```
Expand All @@ -263,7 +263,7 @@ To create a user with a hashed password, you just need to call the hashing funct
|> ifValid \case
Left user -> render NewView { .. }
Right user -> do
hashed <- hashPassword (get #passwordHash user)
hashed <- hashPassword user.passwordHash
user <- user
|> set #passwordHash hashed
|> createRecord
Expand Down Expand Up @@ -347,13 +347,13 @@ import IHP.AuthSupport.Confirm

instance BuildMail (ConfirmationMail User) where
subject = "Confirm your Account"
to ConfirmationMail { .. } = Address { addressName = Nothing, addressEmail = get #email user }
to ConfirmationMail { .. } = Address { addressName = Nothing, addressEmail = user.email }
from = "someone@example.com"
html ConfirmationMail { .. } = [hsx|
Hey,
just checking it's you.

<a href={urlTo (ConfirmUserAction (get #id user) confirmationToken)} target="_blank">
<a href={urlTo (ConfirmUserAction user.id confirmationToken)} target="_blank">
Activate your Account
</a>
|]
Expand Down Expand Up @@ -381,7 +381,7 @@ To send out the confirmation mail, open your registration action. Typically this
|> ifValid \case
Left user -> render NewView { .. }
Right user -> do
hashed <- hashPassword (get #passwordHash user)
hashed <- hashPassword user.passwordHash
user <- user
|> set #passwordHash hashed
|> createRecord
Expand All @@ -391,7 +391,7 @@ To send out the confirmation mail, open your registration action. Typically this


-- We can also customize the flash message text to let the user know that we have sent him an email
setSuccessMessage $ "Welcome onboard! Before you can start, please quickly confirm your email address by clicking the link we've sent to " <> get #email user
setSuccessMessage $ "Welcome onboard! Before you can start, please quickly confirm your email address by clicking the link we've sent to " <> user.email

redirectTo NewSessionAction
```
Expand Down
2 changes: 1 addition & 1 deletion Guide/authorization.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ You can use [`accessDeniedUnless`](https://ihp.digitallyinduced.com/api-docs/IHP
```haskell
action ShowPostAction { postId } = do
post <- fetch postId
accessDeniedUnless (get #userId post == currentUserId)
accessDeniedUnless (post.userId == currentUserId)

render ShowView { .. }
```
2 changes: 1 addition & 1 deletion Guide/controller.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ TODO
Actions have access to the Request Context via the controller context:

```
let requestContext = get #requestContext ?context
let requestContext = ?context.requestContext
```

The Request Context provides access to the Wai request as well as information like the request query and post parameters and the uploaded files. It's usually used by other functions to provide high-level functionality. E.g. the [`getHeader`](https://ihp.digitallyinduced.com/api-docs/IHP-ControllerSupport.html#v:getHeader) function uses the Request Context to access the request headers.
Expand Down
12 changes: 6 additions & 6 deletions Guide/database.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ You can retrieve all records of a table using [`query`](https://ihp.digitallyind
do
users <- query @User |> fetch
forEach users \user -> do
putStrLn (get #name user)
putStrLn user.name
```

This will run a `SELECT * FROM users` query and put a list of `User` structures.
Expand All @@ -165,7 +165,7 @@ When you have the id of a record, you can also use [`fetch`](https://ihp.digital
do
let userId :: Id User = ...
user <- fetch userId
putStrLn (get #name user)
putStrLn user.name
```

This will run the SQL query `SELECT * FROM users WHERE id = ... LIMIT 1`.
Expand Down Expand Up @@ -201,7 +201,7 @@ In this case the field `assigned_user_id` can be null. In our action we want to
```haskell
action ShowTask { taskId } = do
task <- fetch taskId
assignedUser <- case get #assignedUserId task of
assignedUser <- case task.assignedUserId of
Just userId -> do
user <- fetch userId
pure (Just user)
Expand All @@ -213,7 +213,7 @@ This contains a lot of boilerplate for wrapping and unwrapping the [`Maybe`](htt
```haskell
action ShowTask { taskId } = do
task <- fetch taskId
assignedUser <- fetchOneOrNothing (get #assignedUserId task)
assignedUser <- fetchOneOrNothing task.assignedUserId
```

### Fetching `n` records (LIMIT)
Expand Down Expand Up @@ -803,11 +803,11 @@ withTransaction do
company <- newRecord @Company |> createRecord
user <- newRecord @User
|> set #companyId (get #id company)
|> set #companyId company.id
|> createRecord
company <- company
|> setJust #ownerId (get #id user)
|> setJust #ownerId user.id
|> updateRecord
```
Expand Down
16 changes: 8 additions & 8 deletions Guide/file-storage.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,10 @@ action UpdateLogoAction = do
|> fromMaybe (error "No file given")

storedFile <- storeFile file "logos"
let url = get #url storedFile
let url = storedFile.url
```

This will upload the provided `<input type="file" name="logo"/>` to the `logos` directory. The [`storeFile`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-ControllerFunctions.html#v:storeFile) function returns [`StoredFile`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-Types.html#t:StoredFile) structure. We use `get #url` to read the url where the file was saved to.
This will upload the provided `<input type="file" name="logo"/>` to the `logos` directory. The [`storeFile`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-ControllerFunctions.html#v:storeFile) function returns [`StoredFile`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-Types.html#t:StoredFile) structure. We use `storedFile.url` to read the url where the file was saved to.

There's also a [`storeFileWithOptions`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-ControllerFunctions.html#v:storeFileWithOptions) to pass additional configuration:

Expand All @@ -397,7 +397,7 @@ let options :: StoreFileOptions = def
}

storedFile <- storeFileWithOptions file options
let url = get #url storedFile
let url = storedFile.url
```

### Accessing Uploaded Files without Storing them
Expand All @@ -409,7 +409,7 @@ action SubmitMarkdownAction = do
let content :: Text =
fileOrNothing "markdown"
|> fromMaybe (error "no file given")
|> get #fileContent
|> (.fileContent)
|> cs -- content is a LazyByteString, so we use `cs` to convert it to Text

-- We can now do anything with the content of the uploaded file
Expand All @@ -430,14 +430,14 @@ To upload a file to this action you can use the following form:

### Accessing Uploaded File Name

You can get the uploaded file name with `get #fileName file`
You can get the uploaded file name with `file.fileName`

```haskell
action SubmitMarkdownAction = do
let fileName :: Text =
fileOrNothing "markdown"
|> fromMaybe (error "no file given")
|> get #fileName
|> (.fileName)
|> cs

putStrLn fileName
Expand All @@ -450,8 +450,8 @@ When your S3 bucket is not configured for public read access, you need use a tem
```haskell
signedUrl <- createTemporaryDownloadUrlFromPath "logos/8ed22caa-11ea-4c45-a05e-91a51e72558d"

let url :: Text = get #url signedUrl
let expiredAt :: UTCTime = get #expiredAt signedUrl
let url :: Text = signedUrl.url
let expiredAt :: UTCTime = signedUrl.expiredAt
```

If the [`StaticDirStorage`](https://ihp.digitallyinduced.com/api-docs/IHP-FileStorage-Types.html#t:FileStorage) is used, a unsigned normal URL will be returned, as these files are public anyways.
Expand Down
18 changes: 9 additions & 9 deletions Guide/form.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -539,9 +539,9 @@ instance CanSelect User where
-- Here we specify that the <option> value should contain a `Id User`
type SelectValue User = Id User
-- Here we specify how to transform the model into <option>-value
selectValue = get #id
selectValue user = user.id
-- And here we specify the <option>-text
selectLabel = get #name
selectLabel user = user.name
```

Given the above example, the rendered form will look like this:
Expand All @@ -561,7 +561,7 @@ If you want a certain value to be preselected, set the value in the controller.
```haskell
action NewProjectAction = do
users <- query @User |> fetch
let userId = headMay users |> maybe def (get #id)
let userId = headMay users |> maybe def (.id)
let target = newRecord @Project |> set #userId userId
render NewView { .. }
```
Expand All @@ -573,9 +573,9 @@ Sometimes we want to allow the user to specifically make a choice of missing/non
```haskell
instance CanSelect (Maybe User) where
type SelectValue (Maybe User) = Maybe (Id User)
selectValue (Just user) = Just (get #id user)
selectValue (Just user) = Just user.id
selectValue Nothing = Nothing
selectLabel (Just user) = get #name user
selectLabel (Just user) = user.name
selectLabel Nothing = "(none selected)"
```

Expand Down Expand Up @@ -783,8 +783,8 @@ options formContext =
formContext
|> set #customFormAttributes [ ("data-post-id", show postId) ]
where
post = get #model formContext
postId = get #id post
post = formContext.model
postId = post.id
```

The generated HTML will look like this:
Expand Down Expand Up @@ -884,15 +884,15 @@ renderForm post = [hsx|
<label>
Title
</label>
<input type="text" name="title" value={get #title post} class={classes ["form-control", ("is-invalid", isInvalidTitle)]}/>
<input type="text" name="title" value={post.title} class={classes ["form-control", ("is-invalid", isInvalidTitle)]}/>
{titleFeedback}
</div>

<div class="form-group">
<label>
Body
</label>
<input type="text" name="body" value={get #body post} class={classes ["form-control", ("is-invalid", isInvalidBody)]}/>
<input type="text" name="body" value={post.body} class={classes ["form-control", ("is-invalid", isInvalidBody)]}/>
{bodyFeedback}
</div>
|]
Expand Down
14 changes: 7 additions & 7 deletions Guide/helpful-tips.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ IHP uses many "exotic" haskell features. Here's a short explanation of the most
IHP uses hash symbols `#` all over the place, like in this code:

```haskell
set #companyId (get #companyId company)
set #companyId company.id
```

The hashes are provided by the [`OverloadedLabels` language extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_labels.html).
Expand Down Expand Up @@ -169,7 +169,7 @@ Let's say you are working with a controller `ApplicationsAction` and most action

-- Access Control
jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))

...

Expand All @@ -178,7 +178,7 @@ Let's say you are working with a controller `ApplicationsAction` and most action

-- Access Control
jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))

...
```
Expand All @@ -188,7 +188,7 @@ We could start by refactoring the access control logic into a function:
```haskell
accessDeniedUnlessJobPositionAllowed jobPosition = do
jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))
```

And then add a type declaration:
Expand All @@ -197,7 +197,7 @@ And then add a type declaration:
accessDeniedUnlessJobPositionAllowed :: JobPosition -> IO ()
accessDeniedUnlessJobPositionAllowed jobPosition = do
jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))
```

However, GHC will give us an error message stating:
Expand All @@ -212,11 +212,11 @@ Application/Helper/Controller.hs:51:21: error:
jobPositions <- currentCompanyJobPositions
In the expression:
do jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))
In an equation for `accessDeniedUnlessJobPositionAllowed':
accessDeniedUnlessJobPositionAllowed jobPosition
= do jobPositions <- currentCompanyJobPositions
accessDeniedUnless (get #id jobPosition `elem` (ids jobPositions))
accessDeniedUnless (jobPosition.id `elem` (ids jobPositions))
|
51 | jobPositions <- currentCompanyJobPositions
|
Expand Down
6 changes: 3 additions & 3 deletions Guide/hsx.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,19 @@ In HSX you usually write it like this:

```haskell
render user = [hsx|
<p>Hi {get #name user}</p>
<p>Hi {user.name}</p>
{renderCountry}
|]
where
renderCountry = case get #country user of
renderCountry = case user.country of
Just country -> [hsx|<p><small>{country}</small></p>|]
Nothing -> [hsx||]
```

What about if the country is a empty string? A simple solution could look like this:

```haskell
renderCountry = case get #country user of
renderCountry = case user.country of
Just "" -> [hsx||]
Just country -> [hsx|<p>from {country}!</p>|]
Nothing -> [hsx||]
Expand Down
Loading

0 comments on commit e7750f0

Please sign in to comment.