Chore wheel
From Lift
Contents |
Chore wheel
Last edit: Nov 23rd, 2007. Lift is changing day-by-day so this may get stale.
This is a tutorial on making a simple Lift web app. Actually it is my diary as I try to make a simple Lift web app. I am an experienced programmer, familiar with Lisp, Python and Django, Java, some Ruby on Rails and Haskell, but new to Scala and Lift. As such I hope this is useful to others who are trying to get started. People who know better than me and would like to suggest revisions are encouraged to do so, especially if they can answer my questions (marked with ???? in the text.)
The app in question is a "chore wheel" to keep me and my roommates on top of house chores. My hare-brained theory is as follows: most chore systems are based on guilt and negative feelings. Instead, have a chore system based on positive feedback, to give incentives to do random chore goodness. So: no assigned chores, you get points for chores, and people can sort out who's lagging. The website will be a quick way for people to post their chore points.
Install
The recommended way to operate is Eclipse and Maven. I personally have no clue how these whales work, so I'm just repeating black box incantations here.
- Install Scala 2.6.
- Install Eclipse 3.3 and the Scala plugin (you can do this via Help -> Software Updates -> Find and Install. Intuitive, huh?)
- Install Maven2.
I have to say that Eclipse support for Scala is woefully lacking. Other than syntax highlighting and indentation, you don't get much. This shouldn't deter you from using Scala; use emacs + maven if that's your cup of tea. (It would be mine ordinarily but I'm trying to do things by-the-book, plus I'm not sure about the status of the emacs scala-mode.)
Create your project
My Eclipse workspace is located in ~/devel/java/workspace, so to get a new lift project "ChoreWheel" in the org.my package do
cd ~/devel/java/workspace/ mvn archetype:create -U \ -DarchetypeGroupId=net.liftweb \ -DarchetypeArtifactId=lift-archetype-basic \ -DarchetypeVersion=0.9 \ -DremoteRepositories=http://scala-tools.org/repo-releases \ -DgroupId=org.my -DartifactId=ChoreWheel cd ChoreWheel mvn jetty:run
You can now point your web browser to http://localhost:8080/ and be impressed with yourself. Ctrl-C kills jetty. More complete direcions can be found at HowTo start a new liftwebapp.
To turn this into an Eclipse project, run
mvn eclipse:eclipse
This generates a .project file. But before actually opening ChoreWheel in Eclipse, you should probably have the M2_REPO classpath variable set to ~/.m2/repository or wherever you have it. In Eclipse, you can set classpath variables by Window -> Preferences then Java/Build Path/Classpath Variables. I also manually edit the .project file to make the "scalanature" come before the "javanature"???? Then you can import the ChoreWheel project as you did the lift project.
I'm not sure what's up with the M2_HOME variable????
<TO_UPDATE> I also like to set a LIFT_SVN classpath variable to point to the lift source in liftweb/lift/src. This way you can point Eclipse at the source for the lift JAR as follows: in "Referenced Libraries" in ChoreWheel, find the lift-core JAR and Right click -> Properties -> Java Source Attachment then select Variable... and find the LIFT_SVN variable. Now you can click on classes and see their source. Unfortunately due to the fact that Scala can have many classes per file and Java can't, the Scala compiler will produce many .class files per source file, which can be rather confusing when you're browsing -- MappedEnum.class and MappedInt.class both point to the MappedInt.scala source file, for example.
You can also set the Javadoc Location to liftweb/lift/target/apidocs. After this, a Shift+F2 gives the javadoc for any piece of lift, though that's a little sparse right now. </TO_UPDATE>
Eclipse-heads will probably cringe in horror at my lack of Eclipse-fu -- I welcome your suggestions.
Basic Anatomy
First, a brief overview of a lift webapp. We'll drill down on these topics later on.
On startup, a lift webapp runs bootstrap.liftweb.Boot.boot. This sets up system-wide configuration, like DB connections, the SiteMap (which is the URL routing table / permissions gadget), and whatever special handlers and redirects you need.
Lift uses the Model-View-Controller pattern, though is more like Smalltalk / Seaside or Erlang / Erlyweb than say Rails. First the Controllers: Lift can have multiple independent-but-communicating controllers per interaction. See Some more Rails to Lift code examples from DPP's blog and Lift View First. Lift makes a distinction between snippets and controllers. A snippet is an (almost) state-free Controller, in the Rails sense. A controller is a a long-live state-full Scala Actor (which are much like Erlang Actors) acting as a Controller in the Smalltalk sense. A user session will activate many controllers, which can run various widgets on pages independently. Scala Actors are very lightweight and scale nicely.
The Model part is an ActiveRecord-a-like given in net.liftweb.mapper. Here we see our first Scala-ism: instances of the *Mapper classes encapsulate database rows (roughly), whereas the *MetaMapper objects are singletons acting as meta-object foremen for these mappings. In Java these would be static class members (and more); in Python or Lisp they would be meta-classes. For example, instances of KeyedMapper are primary-keyed things, and the KeyedMetaMapper-inheriting singletons are their meta-object bosses. The Mapped* classes (MappedField, MappedString, et cetera) control member fields of *Mapper instances. Together these give an EDSL for ORM. OMFG!
The View part ????I need to learn???? Views can be implemented by Scala objects, or there is an XHTML templating mechanism; there are facilities for binding XHTML templates to data, and combining XHTML templates. Static data and templates live in the src/main/webapp directory.
An app is typically organized as follows:
in src/main/scala: bootstrap.liftweb.Boot -- setup / configuration org.my.model.* -- Models org.my.view.* -- custom Views (as opposed to templates) org.my.snippet.* -- state-less snippets org.my.controller.* -- state-full controllers in src/main/webapp/ -- XHTML templates images/, scripts/ and style/ -- static content templates-hidden/ -- template pieces to mix together, like boilerplate
To summarize a typical request, a user asks for a URL. The appropriate view is determined by the SiteMap, which has been set in bootstrap.liftweb.Boot. Typically the view will be an XHTML template. In turn, the view finds the various controller Actors or snippets it refers to, or instantiates new ones if needed. The controllers and snippets do their business logic, possibly talking to a database via the mapper model, given by *Mapper instances. The correct instances are usually found by querying *MetaMapper foremen. The *Mapper instances in turn query various fields in the database via the Mapped* fields. Once all the business logic is finished, the page is computed and returned to the user. Repeat, ad infinitum (hopefully.)
This of course is complicated by many things: form POSTs and validation, AJAX or COMET calls, redirects / authentication, error handling, template / view combination, et cetera.
Chore Model
First we model the data for our application. Our app will have users and chores. A user is comprised of the usual stuff: name, email, password, et cetera. On first approximation, chores will be comprised of a user (who did it), a date (when it happened), a string (description), and a numeric value (a score.)
The sample new project already comes with a my.org.model.User model class which we will use.
Later: kill Chore description, Chores types are updatable enumerated types + score multipliers. User HasManyThrough Chores????
Later later: how to complicate the model.....multiple houses? apartment building chores?
Set SiteMap.
...to be continued...
View + Controller
First: Kill default CSS. REST / CRUD. Next: AJAX. Finally: reporting, lucene?
Notes for the remainder
- net.liftweb.mapper:
- pattern: Mapper is an ActiveObject, Meta* is the meta-class
- KeyedMapper is a primary-keyed thing
- Indexed things are ??? (right now, only Long, but one could do a String index as well - dpp)
- last: Boot:
- set up DB
- set up sitemap
- wtf is net.liftweb.util.Can? -- a container type for the mapper. why is it needed? (Can is like the Scala Option class, but it's got a Failure subclass as well so when one bails out of a for comprehension, one can note the reason - dpp) (the relevant link (from when lift used Option instead of Can, so a bit outdated): http://blog.lostlake.org/index.php?/archives/50-The-Scala-Option-class-and-how-lift-uses-it.html --j)
- is there a MappedFloat? (Doesn't exist... but it's easy to write :-) dpp) (write my own)
- Many-to-Many or other complicated relationships? Odd queries? Perhaps ChoreWheel is not complicated enough.
- wtf is up with self types in net.liftweb.mapper.Mapper? (type safety for query building - dpp)
- sitemap: points urls to templates not functions

