Да, я смотрел, в том числе, и этот вариант. Более того - именно его я взял за основу, усовершенствовав его.
Усовершенствования касаются прежде всего использования extensible records. Поясню, что я имею в виду.
Model
у меня состоит из трёх главных частей, каждая из которых представлена в виде extensible record'а:
1. Информация, необходимая для идентификации пользователя, который лазает по странице - его userId, token и список его ролей, в виде
LoggedIn { userId : String, ... }
. Эта информация таскается по всем частям приложения, т.к. она необходима везде. Когда это информации нет (например, юзер не залогинен или не зарегистрирован), вместо неё подставляется
AnonimousUser
.
2. Информация о user input на той или иной странице. Например, когда юзер заполняет поля в своем профиле, все введенные им данные хранятся тут, а потом, после валидации и проверки данных на полноту, отправляются на сервер, чтобы создать нового юзера или отредактировать существующий профиль.
Поскольку у нас на разных страницах разный user input, то он у меня реализован в виде union type - для каждого типа страницы своя ветка.
Сам по себе user input у меня тоже реализован в виде extensible record, т.к. мы же его потом отправляем на сервер, а там сервер добавляет к нему служебные поля тип ID, createdAt, updatedAt и т.д. - и всё, что юзер ввёл + служебные поля мы затем получаем в виде данных с сервера. Соответственно, данные с сервера у меня расширяют дополнительными полями данные user input'а.
3. Ну и, собственно, данные с сервера. Они нужны для каждой страницы свои, поэтому представлены также в виде union type.
В итоге модель у меня выглядит так:
type alias Model =
AuthData (PageData (UserInputData {}))
type alias AuthData a =
{ a | user : User }
type alias PageData a =
{ a | page : Page }
type alias UserInputData a =
{ a | userInput : UserInput }
А транслируется
Model
из этого типа, состоящего из набора extensible records, в это:
type alias Model =
{ user : User
, page : Page
, userInput : UserInput
}