如何验证创建多条记录的表单
How to validate a form that creates multiple records
上下文
我有一个表单视图,其中包含由控制器动态生成的任意数量的输入。提交表单时,每个输入都应创建自己的记录,因此如果有 60 个输入,则应创建 60 个记录。
问题
应该如何验证每一项 inputs/fields?在 IHP 文档中的示例中,单个表单仅创建了 1 条记录,因此我不确定执行此操作的最佳或惯用方法是什么。
可能我可以将如下所示的函数映射到每个提交的输入,但左侧情况将由第一次验证失败而不是所有验证失败触发,因此我需要将每个失败保存在列表中(?)在重定向到上一个表单视图之前。
action CreatePostAction = do
let post = newRecord @Post
post
|> fill @'["title", "body"]
|> validateField #title nonEmpty
|> validateField #body nonEmpty
|> ifValid \case
Left post -> render NewView { post }
Right post -> do
post <- post |> createRecord
setSuccessMessage "Post created"
redirectTo PostsAction
尝试这样的事情:
action CreatePostAction = do
let titles :: [Text] = paramList "title"
let bodys :: [Text] = paramList "body"
let posts = zip titles bodys
|> map (\(title, body) -> newRecord @Post
|> set #title title
|> set #body body
|> validateField #title nonEmpty
|> validateField #body nonEmpty
)
validatedPosts :: [Either Post Post] <- forM posts (ifValid (\post -> pure post))
case Either.partitionEithers validatedPosts of
([], posts) -> do
createMany posts
setSuccessMessage "Post created"
redirectTo PostsAction
(invalidPosts, validPosts) -> render NewView { posts }
为此,您需要这样的视图:
module Web.View.Posts.New where
import Web.View.Prelude
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
data NewView = NewView { posts :: [Post] }
instance View NewView where
html NewView { .. } = [hsx|
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href={PostsAction}>Posts</a></li>
<li class="breadcrumb-item active">New Post</li>
</ol>
</nav>
<h1>New Post</h1>
<form id="main-form" method="POST" action={CreatePostAction}>
<input type="submit" class="btn btn-primary"/>
{forEach posts renderForm}
</form>
|]
renderForm :: Post -> Html
renderForm post = [hsx|
<div class="form-group">
<label>
Title
</label>
<input type="text" name="title" value={get #title post} 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)]}/>
{bodyFeedback}
</div>
|]
where
isInvalidTitle = isJust (getValidationFailure #title post)
isInvalidBody = isJust (getValidationFailure #body post)
titleFeedback = case getValidationFailure #title post of
Just result -> [hsx|<div class="invalid-feedback">{result}</div>|]
Nothing -> mempty
bodyFeedback = case getValidationFailure #body post of
Just result -> [hsx|<div class="invalid-feedback">{result}</div>|]
Nothing -> mempty
我的 NewPostAction
看起来像这样:
action NewPostAction = do
let post = newRecord
let posts = take (paramOrDefault 2 "forms") $ repeat post
render NewView { .. }
上下文
我有一个表单视图,其中包含由控制器动态生成的任意数量的输入。提交表单时,每个输入都应创建自己的记录,因此如果有 60 个输入,则应创建 60 个记录。
问题
应该如何验证每一项 inputs/fields?在 IHP 文档中的示例中,单个表单仅创建了 1 条记录,因此我不确定执行此操作的最佳或惯用方法是什么。
可能我可以将如下所示的函数映射到每个提交的输入,但左侧情况将由第一次验证失败而不是所有验证失败触发,因此我需要将每个失败保存在列表中(?)在重定向到上一个表单视图之前。
action CreatePostAction = do
let post = newRecord @Post
post
|> fill @'["title", "body"]
|> validateField #title nonEmpty
|> validateField #body nonEmpty
|> ifValid \case
Left post -> render NewView { post }
Right post -> do
post <- post |> createRecord
setSuccessMessage "Post created"
redirectTo PostsAction
尝试这样的事情:
action CreatePostAction = do
let titles :: [Text] = paramList "title"
let bodys :: [Text] = paramList "body"
let posts = zip titles bodys
|> map (\(title, body) -> newRecord @Post
|> set #title title
|> set #body body
|> validateField #title nonEmpty
|> validateField #body nonEmpty
)
validatedPosts :: [Either Post Post] <- forM posts (ifValid (\post -> pure post))
case Either.partitionEithers validatedPosts of
([], posts) -> do
createMany posts
setSuccessMessage "Post created"
redirectTo PostsAction
(invalidPosts, validPosts) -> render NewView { posts }
为此,您需要这样的视图:
module Web.View.Posts.New where
import Web.View.Prelude
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
data NewView = NewView { posts :: [Post] }
instance View NewView where
html NewView { .. } = [hsx|
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href={PostsAction}>Posts</a></li>
<li class="breadcrumb-item active">New Post</li>
</ol>
</nav>
<h1>New Post</h1>
<form id="main-form" method="POST" action={CreatePostAction}>
<input type="submit" class="btn btn-primary"/>
{forEach posts renderForm}
</form>
|]
renderForm :: Post -> Html
renderForm post = [hsx|
<div class="form-group">
<label>
Title
</label>
<input type="text" name="title" value={get #title post} 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)]}/>
{bodyFeedback}
</div>
|]
where
isInvalidTitle = isJust (getValidationFailure #title post)
isInvalidBody = isJust (getValidationFailure #body post)
titleFeedback = case getValidationFailure #title post of
Just result -> [hsx|<div class="invalid-feedback">{result}</div>|]
Nothing -> mempty
bodyFeedback = case getValidationFailure #body post of
Just result -> [hsx|<div class="invalid-feedback">{result}</div>|]
Nothing -> mempty
我的 NewPostAction
看起来像这样:
action NewPostAction = do
let post = newRecord
let posts = take (paramOrDefault 2 "forms") $ repeat post
render NewView { .. }