packages feed

happs-tutorial-0.4.4: templates/macidupdatesandqueries.st

<h3>Macid updates and queries</h3>

<p>Clicking <a href="/tutorial/initializedummydata">initializedummydata</a> passed off control to 
  spAddDummyData in <a href="/src/Controller.hs">Controller.hs</a>. Keep that .hs file open in another window,
  scrolled down to spAddDummyData, to follow along.

<p>In spAddDummyData, pay particular attention to these two statements

<ol>
  <li>us <- query AskDatastore 
  <li>update InitializeDummyData
</ol>

<p>There's a lot of type hackery and template haskell machinery behind those two lines. 
   But operationally, here's what is happening.

<p>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.

<p>The macid state of our job board is defined in AppStateSetBased</a>,
   in the AppState data declaration. As you may recall, we have seen AppState before, in runserver,
   in our lesson on the <a href="/tutorial/main-function">main module</a>.
   AppState's use in startSystemState, in Main.hs, is what makes it the primary state component of our web app.

<p>From <a href="/src/AppStateSetBased.hs">AppStateSetBased.hs</a>
     (for optimal learning, open this file in another window):

<i>
<p>-- Think of appdatastore as the database in a traditional web app.
<br>-- Data there gets stored permanently
<br>-- Data in appsessions is stored permanently too, but we don't care as much about its persistence,
<br>-- it's just to keep track of who is logged in at a point in time.
<br>data AppState = AppState {
<br>&nbsp;&nbsp;  appsessions :: Sessions SessionData,  
<br>&nbsp;&nbsp;  appdatastore :: S.Set User
<br>&nbsp;&nbsp; deriving (Show,Read,Typeable,Data)    
<br>instance Version AppState
<br>\$(deriveSerialize ''AppState) 
</i>

<p>This is the pattern for a data declaration for data that will be used by the macid state system.

<ul>
  <li>You make the data declaration pretty much in the usual way
  <li>You set the necessary LANGUAGE pragmas for the macid machinery to work (see top of AppStateSetBased.hs)
  <li>You derive Read, Show, Typeable, and Data
  <li>You declare your data type an instance of Version (to allow migrations)
  <li>You have template haskell generate boilerplate that makes AppState an instance of Serializeable.
</ul>

<p>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
   <a href="/src/SerializeableUsers.hs">SerializeableUsers.hs</a> and
   <a href="/src/SerializeableSessions.hs">SerializeableSessions.hs</a>.
   As long as each subcomponent has been declared in the right way, components depending upon them can
   be derived more of less hassle-free.
   
<p>Because AppState is the top-level component, in addition to Read / Show / Serializeable / Version,
   it is also an instance of Method and Component.

<p>The Component instance just defines an initial value for macid: no users, no sessions.

<p>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. 

<p>In the dummy data insertion above, we saw an example of this, with
   <i>query AskDatastore</i> and <i>update InitializeDummyData</i>.

<p>AskDatastore and InitializeDummyData are generated with template haskell, based on the definitions of 
   askDatastore and initializeDummyData, in AppStateSetBased.

<p>If you do

<p>*Main> :browse AppStateSetBased

<p>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.

<p>Think of query actions as the macid equivalent of a select statement in sql, and update as the equivalent
   of update/delete.

<p>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 <a href="/tutorial/ghci-floundering-askdatastore">flounder</a> around enough in ghci you can
   fit askDatastore into a concrete type that makes this explicit.

<p>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.

<p>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.

<p>*Main> :i AskDatastore
<br>data AskDatastore = AskDatastore
<br>&nbsp;&nbsp;        -- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)
<br>instance Serialize AskDatastore
<br>&nbsp;&nbsp;  -- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)
<br>instance Version AskDatastore
<br>&nbsp;&nbsp;  -- Defined at src/AppStateSetBased.hs:(178,2)-(191,27)

<p>However, if you ask ghci about askDatastore, upon which you know AskDatastore is based -- 
   because of the lowercase -- you <i>can</i> confirm that it is a query.

<p>*Main> :i askDatastore
<br>askDatastore :: Query AppState (Data.Set.Set User)

<p>The same goes for InitializeDummyData.

<p>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.

<p>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. 

<p>To get comfortable with macid

<ul>
  <li>read and understand the .hs files that define the job board state:
        AppStateSetBased.hs, SerializeableUsers.hs, and SerializeableSessions.hs
  <li>Play with your locally installed job board while reading along with the
      source code. See how queries and updates are used in the job board:
      when logging in, when creating or deleting jobs,
      when editing a user profile, and so on.
  <li>start using HAppS with macid for your own web apps
</ul>