Discussion:
[elm-discuss] Discovery: Model /= Database
Kasey Speakman
2017-04-19 23:28:05 UTC
Permalink
I'm probably slow, but in recent months I've discovered that trying to use
Elm's Model like a database or cache (as I have previously seen suggested)
has turned out to be pretty painful for me. An example database-minded
model where a section could display *either* a list of employees *or* a
list of courses.

type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}

The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.

Instead, I have been moving toward the model only representing the current
state of my UI. The big difference here is the model representing the
current *visual* elements and their data. This leads more to using union
types to represent parts of the UI. When you switch to a different case of
the union type, the data from the previous case is *dropped on the floor*.
This leaves nothing to remember to "reset". RemoteData is a good
micro-example of this. If there was an error fetching the data, when the
user requests the data again, you switch back to Loading, the error message
is dropped on the floor. No forgetting to hide it.

type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a

If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)

So working towards a Model reflecting the visuals on the page has been an
interesting challenge. I'm not claiming it's easier, but so far I've found
it avoids a class of problems, and has led to some interesting discoveries
in my own apps. One small example: I realized that my LoggedIn and
NotLoggedIn routes should actually be separate "apps". Attempts to model
this in a SPA fashion with the LoggedIn and NotLoggedIn routes as siblings
always came up with the conundrum: how do I make it a compiler error for
the model to be in LoggedIn mode but I receive a NotLoggedIn message, or
vice versa? Even using TEA, I could not avoid this situation. Then I
realized the only way to do that would be as separate apps. And that it was
entirely possible to separate them. My "login page" turned out to be an
entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.

I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Dustin Farris
2017-04-20 16:38:25 UTC
Permalink
Interesting approach to just cache things in local storage and use the
Model as a view "state" only.

I keep a nice store in my Model, but I think a key difference is that I'm
using websockets to update information automatically. Each route (page)
has a list of channels that it cares about, and when data comes in on those
channels, the store is updated.

WRT your issue on authenticated routes, my approach was to write a function
that checks the model for a Maybe String which is the JWT token. If it was
Nothing, I'd send a Cmd to navigate to the login page. It has worked well
so far.
Post by Kasey Speakman
I'm probably slow, but in recent months I've discovered that trying to use
Elm's Model like a database or cache (as I have previously seen suggested)
has turned out to be pretty painful for me. An example database-minded
model where a section could display *either* a list of employees *or* a
list of courses.
type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}
The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.
Instead, I have been moving toward the model only representing the current
state of my UI. The big difference here is the model representing the
current *visual* elements and their data. This leads more to using union
types to represent parts of the UI. When you switch to a different case of
the union type, the data from the previous case is *dropped on the floor*.
This leaves nothing to remember to "reset". RemoteData is a good
micro-example of this. If there was an error fetching the data, when the
user requests the data again, you switch back to Loading, the error message
is dropped on the floor. No forgetting to hide it.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)
So working towards a Model reflecting the visuals on the page has been an
interesting challenge. I'm not claiming it's easier, but so far I've found
it avoids a class of problems, and has led to some interesting discoveries
in my own apps. One small example: I realized that my LoggedIn and
NotLoggedIn routes should actually be separate "apps". Attempts to model
this in a SPA fashion with the LoggedIn and NotLoggedIn routes as siblings
always came up with the conundrum: how do I make it a compiler error for
the model to be in LoggedIn mode but I receive a NotLoggedIn message, or
vice versa? Even using TEA, I could not avoid this situation. Then I
realized the only way to do that would be as separate apps. And that it was
entirely possible to separate them. My "login page" turned out to be an
entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.
I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Rehno Lindeque
2017-04-23 14:01:21 UTC
Permalink
Hi Kasey, I just wanted to mention that the wikipedia article
Model-view-viewmodel
<https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel> might
be helpful reading. In some architectural patterns this is called the
viewmodel rather than the model. (A couple of GUI oriented packages call
this State, for the same reason I think).

At my workplace we tend to use the convention that models should be named
after screen elements, e.g. PartEditor.Model whereas the response from the
server would just be called Part (even though this record is closer to the
database model).

-Editor, -Report, -Chart, -Table, -Matrix, -Page, -Viewer are of a couple
of suffixes I'm tending to reach for when naming the modules that contain
viewmodels.
Post by Dustin Farris
Interesting approach to just cache things in local storage and use the
Model as a view "state" only.
I keep a nice store in my Model, but I think a key difference is that I'm
using websockets to update information automatically. Each route (page)
has a list of channels that it cares about, and when data comes in on those
channels, the store is updated.
WRT your issue on authenticated routes, my approach was to write a
function that checks the model for a Maybe String which is the JWT token.
If it was Nothing, I'd send a Cmd to navigate to the login page. It has
worked well so far.
Post by Kasey Speakman
I'm probably slow, but in recent months I've discovered that trying to
use Elm's Model like a database or cache (as I have previously seen
suggested) has turned out to be pretty painful for me. An example
database-minded model where a section could display *either* a list of
employees *or* a list of courses.
type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}
The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.
Instead, I have been moving toward the model only representing the
current state of my UI. The big difference here is the model representing
the current *visual* elements and their data. This leads more to using
union types to represent parts of the UI. When you switch to a different
case of the union type, the data from the previous case is *dropped on
the floor*. This leaves nothing to remember to "reset". RemoteData is a
good micro-example of this. If there was an error fetching the data, when
the user requests the data again, you switch back to Loading, the error
message is dropped on the floor. No forgetting to hide it.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)
So working towards a Model reflecting the visuals on the page has been an
interesting challenge. I'm not claiming it's easier, but so far I've found
it avoids a class of problems, and has led to some interesting discoveries
in my own apps. One small example: I realized that my LoggedIn and
NotLoggedIn routes should actually be separate "apps". Attempts to model
this in a SPA fashion with the LoggedIn and NotLoggedIn routes as siblings
always came up with the conundrum: how do I make it a compiler error for
the model to be in LoggedIn mode but I receive a NotLoggedIn message, or
vice versa? Even using TEA, I could not avoid this situation. Then I
realized the only way to do that would be as separate apps. And that it was
entirely possible to separate them. My "login page" turned out to be an
entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.
I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Girish Sonawane
2017-04-28 18:27:37 UTC
Permalink
That's interesting. But how do you pass data to `view`? For example say `Employees` list when there is no such list in `Model`? Maybe I am missing something obvious here!
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Girish Sonawane
2017-04-29 15:51:52 UTC
Permalink
Never mind. I re-read your message, you suggested using union types for storing the current state, that makes sense.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Dustin Farris
2017-04-30 06:45:48 UTC
Permalink
I think I've just had an aha moment with this post.

I am in the process of refactoring my monolith MUV into separate modules
with their own MUV for each "page" of my SPA. Up to this point, I have had
a separate Store module with its own Model and Msg types and an update
function (no view, obviously). This has worked well up until now, but
after splitting off the pages of my app, it is getting more cumbersome to
update the Store in a way that looks nice.

e.g. in my Main.elm I'm ending up with something like

update msg model =
case msg of
UserProfilePageMsg msg_ ->
let
( userProfilePageModel, userProfilePageCmd ) =
UserProfilePage.update msg_ model.userProfilePage
in
case msg_ of
UserProfilePage.StoreMsg msg__ ->
let
( storeModel, storeCmd ) =
Store.update msg__ model.store
in
{ model
| userProfilePage = userProfilePageModel
, store = storeModel
}
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd
, Cmd.map StoreMsg storeCmd
]
_ ->
{ model | userProfilePage = userProfilePageModel }
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd ]


and so on for every page that invokes Store.Msg—which is most pages.

I am thinking that there is a better way, and perhaps Kasey's suggestion of
forgoing an in-memory Store on the Model might be it. I'm still not sure—I
do like the snappy feel of a page loading instantly if the data is in
memory—even if it might change after a brief consultation with the server.

Dustin
Post by Kasey Speakman
I'm probably slow, but in recent months I've discovered that trying to use
Elm's Model like a database or cache (as I have previously seen suggested)
has turned out to be pretty painful for me. An example database-minded
model where a section could display *either* a list of employees *or* a
list of courses.
type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}
The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.
Instead, I have been moving toward the model only representing the current
state of my UI. The big difference here is the model representing the
current *visual* elements and their data. This leads more to using union
types to represent parts of the UI. When you switch to a different case of
the union type, the data from the previous case is *dropped on the floor*.
This leaves nothing to remember to "reset". RemoteData is a good
micro-example of this. If there was an error fetching the data, when the
user requests the data again, you switch back to Loading, the error message
is dropped on the floor. No forgetting to hide it.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)
So working towards a Model reflecting the visuals on the page has been an
interesting challenge. I'm not claiming it's easier, but so far I've found
it avoids a class of problems, and has led to some interesting discoveries
in my own apps. One small example: I realized that my LoggedIn and
NotLoggedIn routes should actually be separate "apps". Attempts to model
this in a SPA fashion with the LoggedIn and NotLoggedIn routes as siblings
always came up with the conundrum: how do I make it a compiler error for
the model to be in LoggedIn mode but I receive a NotLoggedIn message, or
vice versa? Even using TEA, I could not avoid this situation. Then I
realized the only way to do that would be as separate apps. And that it was
entirely possible to separate them. My "login page" turned out to be an
entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.
I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Oliver Searle-Barnes
2017-04-30 19:31:49 UTC
Permalink
The pattern we use is to have our Page.update functions return

(model, cmd, storeCmd)

the main update then applies the storeCmd to the Store. (the actual code
supports a few other things but that's the basic gist of it). Hit me up on
slack if you want to chat about it.
Post by Dustin Farris
I think I've just had an aha moment with this post.
I am in the process of refactoring my monolith MUV into separate modules
with their own MUV for each "page" of my SPA. Up to this point, I have had
a separate Store module with its own Model and Msg types and an update
function (no view, obviously). This has worked well up until now, but
after splitting off the pages of my app, it is getting more cumbersome to
update the Store in a way that looks nice.
e.g. in my Main.elm I'm ending up with something like
update msg model =
case msg of
UserProfilePageMsg msg_ ->
let
( userProfilePageModel, userProfilePageCmd ) =
UserProfilePage.update msg_ model.userProfilePage
in
case msg_ of
UserProfilePage.StoreMsg msg__ ->
let
( storeModel, storeCmd ) =
Store.update msg__ model.store
in
{ model
| userProfilePage = userProfilePageModel
, store = storeModel
}
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd
, Cmd.map StoreMsg storeCmd
]
_ ->
{ model | userProfilePage = userProfilePageModel }
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd ]
and so on for every page that invokes Store.Msg—which is most pages.
I am thinking that there is a better way, and perhaps Kasey's suggestion
of forgoing an in-memory Store on the Model might be it. I'm still not
sure—I do like the snappy feel of a page loading instantly if the data is
in memory—even if it might change after a brief consultation with the
server.
Dustin
Post by Kasey Speakman
I'm probably slow, but in recent months I've discovered that trying to
use Elm's Model like a database or cache (as I have previously seen
suggested) has turned out to be pretty painful for me. An example
database-minded model where a section could display *either* a list of
employees *or* a list of courses.
type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}
The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.
Instead, I have been moving toward the model only representing the
current state of my UI. The big difference here is the model representing
the current *visual* elements and their data. This leads more to using
union types to represent parts of the UI. When you switch to a different
case of the union type, the data from the previous case is *dropped on
the floor*. This leaves nothing to remember to "reset". RemoteData is a
good micro-example of this. If there was an error fetching the data, when
the user requests the data again, you switch back to Loading, the error
message is dropped on the floor. No forgetting to hide it.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)
So working towards a Model reflecting the visuals on the page has been an
interesting challenge. I'm not claiming it's easier, but so far I've found
it avoids a class of problems, and has led to some interesting discoveries
in my own apps. One small example: I realized that my LoggedIn and
NotLoggedIn routes should actually be separate "apps". Attempts to model
this in a SPA fashion with the LoggedIn and NotLoggedIn routes as siblings
always came up with the conundrum: how do I make it a compiler error for
the model to be in LoggedIn mode but I receive a NotLoggedIn message, or
vice versa? Even using TEA, I could not avoid this situation. Then I
realized the only way to do that would be as separate apps. And that it was
entirely possible to separate them. My "login page" turned out to be an
entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.
I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Eric G
2017-05-01 18:32:33 UTC
Permalink
This topic is a bit all over the place, but just wanted to chime in with a
different approach that I've found works pretty well for updating
database-backed state, especially in contexts where you aren't syncing with
the backend via websockets or similar.

That is to follow the principle: no database-backed state gets updated
directly in the frontend app. Updates are made via *commands *to the
backend (like an extension of Msgs to the backend, essentially). New state
is always loaded from the backend, not from a cache (unless you need that
and can deal with cache invalidation cleanly), preserving the
unidirectional flow of state. In this way, you can minimize the need for a
'store', and never need to directly update data in the 'store' based on
user input. I think this may be related to what Kasey was saying
originally.

Eric
Post by Oliver Searle-Barnes
The pattern we use is to have our Page.update functions return
(model, cmd, storeCmd)
the main update then applies the storeCmd to the Store. (the actual code
supports a few other things but that's the basic gist of it). Hit me up on
slack if you want to chat about it.
Post by Dustin Farris
I think I've just had an aha moment with this post.
I am in the process of refactoring my monolith MUV into separate modules
with their own MUV for each "page" of my SPA. Up to this point, I have had
a separate Store module with its own Model and Msg types and an update
function (no view, obviously). This has worked well up until now, but
after splitting off the pages of my app, it is getting more cumbersome to
update the Store in a way that looks nice.
e.g. in my Main.elm I'm ending up with something like
update msg model =
case msg of
UserProfilePageMsg msg_ ->
let
( userProfilePageModel, userProfilePageCmd ) =
UserProfilePage.update msg_ model.userProfilePage
in
case msg_ of
UserProfilePage.StoreMsg msg__ ->
let
( storeModel, storeCmd ) =
Store.update msg__ model.store
in
{ model
| userProfilePage = userProfilePageModel
, store = storeModel
}
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd
, Cmd.map StoreMsg storeCmd
]
_ ->
{ model | userProfilePage = userProfilePageModel }
! [ Cmd.map UserProfilePageMsg
userProfilePageCmd ]
and so on for every page that invokes Store.Msg—which is most pages.
I am thinking that there is a better way, and perhaps Kasey's suggestion
of forgoing an in-memory Store on the Model might be it. I'm still not
sure—I do like the snappy feel of a page loading instantly if the data is
in memory—even if it might change after a brief consultation with the
server.
Dustin
Post by Kasey Speakman
I'm probably slow, but in recent months I've discovered that trying to
use Elm's Model like a database or cache (as I have previously seen
suggested) has turned out to be pretty painful for me. An example
database-minded model where a section could display *either* a list of
employees *or* a list of courses.
type alias Model =
{ employees : List Employee
, courses : List Course
, loadingError : Maybe Http.Error
, route : MyRoute -- employee or course
}
The problem this runs into is having to worry about state management. I
have to remember to "reset" or "turn off" things when they are not active.
As my application grew, I had a lot of problems that boiled down to tedious
state management details. My cached data didn't turn out to be all that
useful because I usually had to reload it anyway, in case something changed.
Instead, I have been moving toward the model only representing the
current state of my UI. The big difference here is the model representing
the current *visual* elements and their data. This leads more to using
union types to represent parts of the UI. When you switch to a different
case of the union type, the data from the previous case is *dropped on
the floor*. This leaves nothing to remember to "reset". RemoteData is a
good micro-example of this. If there was an error fetching the data, when
the user requests the data again, you switch back to Loading, the error
message is dropped on the floor. No forgetting to hide it.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
If it is really important to cache the data, I prefer to keep that as a
persistence concern, not on Model. It can be part of the process for
retrieving the data to first check my chosen cache before making a request
for fresh data. For instance, first check local storage before making an
HTTP call. (Currently, this scenario is easier with Native modules for lack
of Local Storage API or being able to wait on port subscriptions. But it's
still doable.)
So working towards a Model reflecting the visuals on the page has been
an interesting challenge. I'm not claiming it's easier, but so far I've
found it avoids a class of problems, and has led to some interesting
discoveries in my own apps. One small example: I realized that my LoggedIn
and NotLoggedIn routes should actually be separate "apps". Attempts to
model this in a SPA fashion with the LoggedIn and NotLoggedIn routes as
siblings always came up with the conundrum: how do I make it a compiler
error for the model to be in LoggedIn mode but I receive a NotLoggedIn
message, or vice versa? Even using TEA, I could not avoid this situation.
Then I realized the only way to do that would be as separate apps. And that
it was entirely possible to separate them. My "login page" turned out to be
an entirely self-contained process: the user filling in info, obtaining a
token, and saving it to local storage.
I post this in the slim hope it is helpful to someone.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...