Clicking initializedummydata passed off control to spAddDummyData in Controller.hs. Keep that .hs file open in another window, scrolled down to spAddDummyData, to follow along.
In spAddDummyData, pay particular attention to these two statements
There's a lot of type hackery and template haskell machinery behind those two lines. But operationally, here's what is happening.
spAddDummyData queried the macid state, and checked whether the datastore, which is a set of users, is the empty set. If it's the empty set, there is no existing data and the dummy data can be added safely, and then a "dummy data success" page gets displayed. Otherwise no update happens and you get an error page.
The macid state of our job board is defined in AppStateSetBased, in the AppState data declaration. As you may recall, we have seen AppState before, in runserver, in our lesson on the main module. AppState's use in startSystemState, in Main.hs, is what makes it the primary state component of our web app.
From AppStateSetBased.hs (for optimal learning, open this file in another window):
-- Think of appdatastore as the database in a traditional web app.
-- Data there gets stored permanently
-- Data in appsessions is stored permanently too, but we don't care as much about its persistence,
-- it's just to keep track of who is logged in at a point in time.
data AppState = AppState {
appsessions :: Sessions SessionData,
appdatastore :: S.Set User
deriving (Show,Read,Typeable,Data)
instance Version AppState
\$(deriveSerialize ''AppState)
This is the pattern for a data declaration for data that will be used by the macid state system.
AppState depends on the User and Sessions data types. The User data type in turn depends on the Job and ConsultantProfile data types. Each of these data declarations follows this same pattern, which you should verify for yourself by reading their data declarations in SerializeableUsers.hs and SerializeableSessions.hs. As long as each subcomponent has been declared in the right way, components depending upon them can be derived more of less hassle-free.
Because AppState is the top-level component, in addition to Read / Show / Serializeable / Version, it is also an instance of Method and Component.
The Component instance just defines an initial value for macid: no users, no sessions.
The Method instance is generated by template haskell, by mkMethods. mkMethods also generates the upper case Update and Query data declarations, values of which which are suitable input to query/update within a server part.
In the dummy data insertion above, we saw an example of this, with query AskDatastore and update InitializeDummyData.
AskDatastore and InitializeDummyData are generated with template haskell, based on the definitions of askDatastore and initializeDummyData, in AppStateSetBased.
If you do
*Main> :browse AppStateSetBased
you'll see a number of function/datatype pairs defined via template haskell, where the datatype has the same name as the function, except it is upper-cased. askDatastore / AskDatastore, getUser / GetUser, changePassword / ChangePassword, and so on. In every case, the upper-cased datatype is generated by template haskell, whereas the lowercase function is a normal function.
Think of query actions as the macid equivalent of a select statement in sql, and update as the equivalent of update/delete.
The functions that query actions are based on are in MonadReader. askDatastore puts itself into MonadReader by its use of ask. This is a bit obfuscated by type systems and the complicated types, but if you flounder around enough in ghci you can fit askDatastore into a concrete type that makes this explicit.
The functions that update actions are based on are in MonadState. InitializeDataStore puts itself into MonadState by the use of modify in a function that it depends on.
Alhough it is obvious that AskDatastore is a query action from its name, you can't tell that it's a query action by asking ghci -- only that it is part of the macid system.
*Main> :i AskDatastore
data AskDatastore = AskDatastore
-- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)
instance Serialize AskDatastore
-- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)
instance Version AskDatastore
-- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)
However, if you ask ghci about askDatastore, upon which you know AskDatastore is based -- because of the lowercase -- you can confirm that it is a query.
*Main> :i askDatastore
askDatastore :: Query AppState (Data.Set.Set User)
The same goes for InitializeDummyData.
So remember, if you are reading some happs code and come across a macid action and aren't certain whether it's a query or an update, a quick way to find out is to ask ghci for information about the lowercase function it's based on.
Macid is one of the most powerful features of HApps, but I found it hard to learn. There are a lot of types, and those signatures can get cryptic. It took me a while to get as comfortable with the macid way of doing things as I was with sql. But when I finally got it I was glad I made the effort. I really feel that this is a much more powerful, straightforward way of laying out a web app's functionality, and that I will win back the invested time with more rapid development of future websites.
To get comfortable with macid