Search code examples
haskellihp

Factoring out controller actions


Context

Here's the PostsController from the blog example in the IHP guide:

instance Controller PostsController where
    action PostsAction = do
        posts <- query @Post
            |> orderByDesc #createdAt
            |> fetch
        render IndexView { .. }

    action NewPostAction = do
        let post = newRecord
        render NewView { .. }

    action ShowPostAction { postId } = do
        post <- fetch postId
            >>= pure . modify #comments (orderByDesc #createdAt)
            >>= fetchRelated #comments 
        render ShowView { .. }

    action EditPostAction { postId } = do
        post <- fetch postId
        render EditView { .. }

    action UpdatePostAction { postId } = do
        post <- fetch postId
        post
            |> buildPost
            |> ifValid \case
                Left post -> render EditView { .. }
                Right post -> do
                    post <- post |> updateRecord
                    setSuccessMessage "Post updated"
                    redirectTo EditPostAction { .. }

    action CreatePostAction = do
        let post = newRecord @Post
        post
            |> buildPost
            |> ifValid \case
                Left post -> render NewView { .. } 
                Right post -> do
                    post <- post |> createRecord
                    setSuccessMessage "Post created"
                    redirectTo PostsAction

    action DeletePostAction { postId } = do
        post <- fetch postId
        deleteRecord post
        setSuccessMessage "Post deleted"
        redirectTo PostsAction

Factoring out an action

Let's suppose I have a controller where one of the action definitions is getting really long and I'd like to maybe put it in a separate file.

Just as an example, let's try to move this one out:

action PostsAction = do
    posts <- query @Post
        |> orderByDesc #createdAt
        |> fetch
    render IndexView { .. }

I tried to setup a separate standalone function:

actionPostsAction = do
    posts <- query @Post
        |> orderByDesc #createdAt
        |> fetch
    render IndexView { .. }

Then my action definition could just be:

action PostsAction = actionPostsAction

However, when I setup the standalone function I get some errors reported in vscode:

enter image description here

Question

What's a good way to factor out an action? Is there a way to do this?

The guide has a section on Controller & Actions but I didn't notice anything there about splitting out actions. Let me know if I missed something there or if there's another place which talks about this.


Solution

  • I think this is one of the cases where an explicit type signature is necessary, this should work:

    actionPostsAction
      :: ( ?context      :: ControllerContext
         , ?modelContext :: ModelContext
         , ?theAction    :: PostsController
         )
      => IO () 
    actionPostsAction = do
        posts <- query @Post
            |> orderByDesc #createdAt
            |> fetch
        render IndexView { .. }
    

    See also: https://ihp.digitallyinduced.com/Guide/troubleshooting.html#unbound-implicit-parameter-modelcontextmodelcontext