---
title: "Modelling state in Elm to reflect business logic"
description: "Modelling state in Elm to reflect business logic"
canonical_url: "https://www.bigbinary.com/blog/modelling-state-in-elm-to-reflect-business-logic"
markdown_url: "https://www.bigbinary.com/blog/modelling-state-in-elm-to-reflect-business-logic.md"
---

# Modelling state in Elm to reflect business logic

Modelling state in Elm to reflect business logic

- Author: Ritesh Pillai
- Published: June 4, 2018
- Categories: Misc

We recently made
[ApiSnapshot open source](https://blog.bigbinary.com/2018/05/25/apisnapshot-built-using-elm-and-ruby-on-rails-is-open-source.html).
As mentioned in that blog we ported code from React.js to Elm.

One of the features of `ApiSnapshot` is support for `Basic Authentication`.

![ApiSnapshot with basic authentication](https://www.bigbinary.com/blog/images/images_used_in_blog/2018/modelling-state-in-elm-to-reflect-business-logic/apisnapshot-with-basic-authentication.png)

While we were rebuilding the whole application in Elm, we had to port the "Add
Basic Authentication" feature. This feature can be accessed from the "More"
drop-down on the right-hand side of the app and it lets user add username and
password to the request.

Let's see how the `Model` of our Elm app looks.

```elm
type alias Model =
{ request : Request.MainRequest.Model
, response : Response.MainResponse.Model
, route : Route
}
```

Here is the Model in _Request.MainRequest_ module.

```elm
type alias APISnapshotRequest =
{ url : String
, httpMethod : HttpMethod
, requestParameters : RequestParameters
, requestHeaders : RequestHeaders
, username : Maybe String
, password : Maybe String
, requestBody : Maybe RequestBody
}

type alias Model =
{ request : APISnapshotRequest
, showErrors : Bool
}
```

`username` and `password` fields are optional for the users so we kept them as
`Maybe` types.

Note that API always responds with `username` and `password` whether user
clicked to add `Basic Authentication` or not. The API would respond with a
**_null_** for both username and password when a user tries to retrieve a
snapshot for which user did not fill `username` and `password`.

Here is a sample API response.

```json
{
  "url": "http://dog.ceo/api/breed/affenpinscher/images/random",
  "httpMethod": "GET",
  "requestParams": {},
  "requestHeaders": {},
  "requestBody": null,
  "username": "alanturning",
  "password": "welcome",
  "assertions": [],
  "response": {
    "response_headers": {
      "age": "0",
      "via": "1.1 varnish (Varnish/6.0), 1.1 varnish (Varnish/6.0)",
      "date": "Thu, 03 May 2018 09:43:11 GMT",
      "vary": "",
      "cf_ray": "4151c826ac834704-EWR",
      "server": "cloudflare"
    },
    "response_body": "{\"status\":\"success\",\"message\":\"https:\\/\\/images.dog.ceo\\/breeds\\/affenpinscher\\/n02110627_13221.jpg\"}",
    "response_code": "200"
  }
}
```

Let's look at the view code which renders the data received from the API.

```elm
view : (Maybe String, Maybe String) -> Html Msg
view usernameAndPassword =
case usernameAndPassword of
(Nothing, Nothing) -> text ""
(Just username, Nothing) -> basicAuthenticationView username ""
(Nothing, Just password) -> basicAuthenticationView "" password
(Just username, Just password) -> basicAuthenticationView username password

basicAuthenticationView : String -> String -> Html Msg
basicAuthenticationView username password =
[ div [ class "form-row" ]
[ input
[ type_ "text"
, placeholder "Username"
, value username
, onInput (UpdateUsername)
]
[]
, input
[ type_ "password"
, placeholder "Password"
, value password
, onInput (UpdatePassword)
]
[]
, a
[ href "javascript:void(0)"
, onClick (RemoveBasicAuthentication)
]
[ text "×" ]
]
]
```

To get the desired view we apply following rules.

1. Check if both the values are string.
2. Check if either of the values is string.
3. Assume that both the values are `null`.

This works but we can do a better job of modelling it.

What's happening here is that we were trying to translate our API responses
directly to the Model . Let's try to club username and password together into a
new type called _BasicAuthentication_.

In the model add a parameter called _basicAuthentication_ which would be of type
`Maybe BasicAuthentication`. This way if user has opted to use basic
authentication fields then it is a _Just BasicAuthentication_ and we can show
the input boxes. Otherwise it is _Nothing_ and we show nothing!

Here is what the updated Model for _Request.MainRequest_ would look like.

```elm
type alias BasicAuthentication =
{ username : String
, password : String
}

type alias APISnapshotRequest =
{ url : String
, httpMethod : HttpMethod
, requestParameters : RequestParameters
, requestHeaders : RequestHeaders
, basicAuthentication : Maybe BasicAuthentication
, requestBody : Maybe RequestBody
}

type alias Model =
{ request : APISnapshotRequest
, showErrors : Bool
}
```

Elm compiler is complaining that we need to make changes to JSON decoding for
_APISnapshotRequest_ type because of this change.

Before we fix that let's take a look at how JSON decoding is currently being
done.

```elm
import Json.Decode as JD
import Json.Decode.Pipeline as JP

decodeAPISnapshotRequest : Response -> APISnapshotRequest
decodeAPISnapshotRequest hitResponse =
let
result =
JD.decodeString requestDecoder hitResponse.body
in
case result of
Ok decodedValue ->
decodedValue

            Err err ->
                emptyRequest

requestDecoder : JD.Decoder APISnapshotRequest
requestDecoder =
JP.decode Request
|> JP.optional "username" (JD.map Just JD.string) Nothing
|> JP.optional "password" (JD.map Just JD.string) Nothing
```

Now we need to derive the state of the application from our API response .

Let's introduce a type called _ReceivedAPISnapshotRequest_ which would be the
shape of our old _APISnapshotRequest_ with no _basicAuthentication_ field. And
let's update our _requestDecoder_ function to return a Decoder of type
_ReceivedAPISnapshotRequest_ instead of _APISnapshotRequest_.

```elm
type alias ReceivedAPISnapshotRequest =
{ url : String
, httpMethod : HttpMethod
, requestParameters : RequestParameters
, requestHeaders : RequestHeaders
, username : Maybe String
, password : Maybe String
, requestBody : Maybe RequestBody
}

requestDecoder : JD.Decoder ReceivedAPISnapshotRequest
```

We need to now move our earlier logic that checks to see if a user has opted to
use the basic authentication fields or not from the view function to the
_decodeAPISnapshotRequest_ function.

```elm
decodeAPISnapshotRequest : Response -> APISnapshotRequest
decodeAPISnapshotRequest hitResponse =
let
result =
JD.decodeString requestDecoder hitResponse.body
in
case result of
Ok value ->
let
extractedCreds =
( value.username, value.password )

                    derivedBasicAuthentication =
                        case extractedCreds of
                            ( Nothing, Nothing ) ->
                                Nothing

                            ( Just receivedUsername, Nothing ) ->
                                Just { username = receivedUsername, password = "" }

                            ( Nothing, Just receivedPassword ) ->
                                Just { username = "", password = receivedPassword }

                            ( Just receivedUsername, Just receivedPassword ) ->
                                Just { username = receivedUsername, password = receivedPassword }
                in
                    { url = value.url
                    , httpMethod = value.httpMethod
                    , requestParameters = value.requestParameters
                    , requestHeaders = value.requestHeaders
                    , basicAuthentication = derivedBasicAuthentication
                    , requestBody = value.requestBody
                    }

            Err err ->
                emptyRequest

```

We extract the username and password into _extractedCreds_ as a Pair from
_ReceivedAPISnapshotRequest_ after decoding and construct our
_APISnapshotRequest_ from it.

And now we have a clean view function which just takes a _BasicAuthentication_
type and returns us a _Html Msg_ type.

```elm
view : BasicAuthentication -> Html Msg
view b =
[ div [ class "form-row" ]
[ input
[ type_ "text"
, placeholder "Username"
, value b.username
, onInput (UpdateUsername)
]
[]
, input
[ type_ "password"
, placeholder "Password"
, value b.password
, onInput (UpdatePassword)
]
[]
, a
[ href "javascript:void(0)"
, onClick (RemoveBasicAuthentication)
]
[ text "×" ]
]
]
```

We now have a Model that better captures the business logic. And should we
change the logic of basic authentication parameter selection in the future, We
do not have to worry about updating the logic in the view .

## Links

- [Human page](https://www.bigbinary.com/blog/modelling-state-in-elm-to-reflect-business-logic)
