Skip to content

v0.11.0 (Beta 13.06.2021)

Compare
Choose a tag to compare
@mpscholten mpscholten released this 13 Jun 13:06
· 2767 commits to master since this release

A new IHP release with some new features (spoiler alert: finally table joins) and bug fixes. Around 150 commits have been merged since the last release 23 days ago 🚀

Major Changes

  • Joins:
    This has been requested for quite some time already. Finally @hllizi solved this: You can now do joins with the IHP query builder 🎉

    tomPosts <- query @Post
            |> innerJoin @User (#authorId, #id)
            |> filterWhereJoinedTable @User (#name, "Tom")
            |> fetch

    You can also join multiple tables:

    query @Posts
            |> innerJoin @Tagging (#id, #postId)
            |> innerJoinThirdTable @Tag @Tagging (#id, #tagId)
            |> filterWhereJoinedTable @Tag (#tagText, "haskell")
            |> fetch

    Type safety is maintained by adding all joined types to a type-level list and checking that the table has been joined where necessary.
    E.g. uses of filterWhereJoinedTable @Model will only compile if @Model had been joined to the input before.

  • Server-Side Components
    We've mixed the ideas of react.js with HSX. IHP Server-Side Components provide a toolkit for building interactive client-side functionality without needing to write too much javascript.

    Here's how a Counter with a Plus One button looks like:

    module Web.Component.Counter where
    
    import IHP.ViewPrelude
    import IHP.ServerSideComponent.Types
    import IHP.ServerSideComponent.ControllerFunctions
    
    -- The state object
    data Counter = Counter { value :: !Int }
    
    -- The set of actions
    data CounterController
        = IncrementCounterAction
        deriving (Eq, Show, Data)
    
    $(deriveSSC ''CounterController)
    
    -- The render function and action handlers
    instance Component Counter CounterController where
        initialState = Counter { value = 0 }
        
        render Counter { value } = [hsx|
            Current: {value} <br />
            <button onclick="callServerAction('IncrementCounterAction')">Plus One</button>
        |]
        
        action state IncrementCounterAction = do
            state
                |> incrementField #value
                |> pure
    
    instance SetField "value" Counter Int where setField value' counter = counter { value = value' }

    Bildschirmvideo aufnehmen 2021-06-13 um 14 07 57

    Here's a demo of using Server-Side Components for a interactive data table:

    Bildschirmvideo aufnehmen 2021-06-13 um 14 11 05

    Learn more about Server-Side Components in the documentation.

  • New IHP.FileStorage Module: For storing files in AWS S3 and other Cloud Storage Services

    A common task when building web applications is to save and manage uploaded files like custom logos, profile pictures or .csv files provided by the user. IHP now provides a simple file storage system to upload files to Amazon S3 or any S3 compatible cloud service.

    Here's how the new API looks, once configured:

    action UpdateCompanyAction { companyId } = do
        company <- fetch companyId
        company
            |> fill @'["name"]
            |> uploadToStorage #logoUrl
            >>= ifValid \case
                Left company -> render EditView { .. }
                Right company -> do
                    company <- company |> updateRecord
                    redirectTo EditCompanyAction { .. }

    Some of the cool features:

    • For pictures, use the ImageMagick preprocessor to resize and convert the pictures
    • Generate signed download URLs for private files
    • You can use the static/ directory instead of uploading to S3 in development

    Learn more in the documentation.

  • Case Insensitive Operations:
    You can now use filterWhereCaseInsensitive to query for strings in a case insensitive way:

    userByEmail :: Text -> IO (Maybe User)
    userByEmail email = do
        user <- query @User
                |> filterWhereCaseInsensitive (#email, email)
                |> fetchOneOrNothing
        -- Query: `SELECT * FROM users WHERE LOWER(email) = <email>`
        pure user

    There's also a new validateIsUniqueCaseInsensitive for checking uniqueness in a case insensitive way:

    action CreateUserAction = do
        let user = newRecord @User
        user
            |> fill @'["email"]
            |> validateIsUniqueCaseInsensitive #email
            >>= ifValid \case
                Left user -> render NewView { .. }
                Right user -> do
                    createRecord user
                    redirectTo UsersAction

    Important if you use IHP's Login: IHP's built-in sessions controller used for the built-in login now uses case-insensitive lookup for the email addresses at login. This will improve user experience for users that create their account with [email protected] and then try to log in using [email protected].

Other Changes

Updating

See the UPGRADE.md for upgrade instructions.

If you have any problems with updating, let us know on the IHP forum.

📧 To stay in the loop, subscribe to the IHP release emails. Or follow digitally induced on twitter.