]> git.decadent.org.uk Git - maypole.git/commitdiff
Lots more documentation.
authorSimon Cozens <simon@simon-cozens.org>
Thu, 15 Apr 2004 14:10:27 +0000 (14:10 +0000)
committerSimon Cozens <simon@simon-cozens.org>
Thu, 15 Apr 2004 14:10:27 +0000 (14:10 +0000)
git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@135 48953598-375a-da11-a14b-00016c27c3ee

doc/About.pod
doc/Beer.pod
doc/Request.pod

index b5cf3e7a2774f89ffb36875eef06f2ba0ae9c032..074f4bc1a223810e197a26c4a5447b7c5ba4b4ed 100644 (file)
 
 =head1 Introduction to the Maypole Request Model
 
-=head2 What is MVC for web applications?
+This chapter serves as a gentle introduction to Maypole and setting up
+Maypole applications. We look at Maypole is, how to get it up and
+running, and how to start thinking about building Maypole applications.
 
 =head2 What is Maypole?
 
+Presumably you have some idea of what Maypole is all about, or otherwise
+you wouldn't be reading this manual. But Maypole is good at many
+different things, and you may have accidentally focussed on one aspect
+of Maypole while missing the big picture.
+
+For instance, you may know that Maypole is extremely good at putting web
+front-ends onto databases. This is true, but it's only a part of what
+Maypole does. You may have heard that Maypole is a web application
+framework, which is true, but it doesn't mean very much. There are a
+huge number of things that Maypole can do, because it's very much a
+blank slate. You can make it do what you will. In this manual, we'll be
+making it act as a front-end to a database, as a social network site, as
+an intranet portal, and many other things besides. It is a framework.
+
+I like to think that Maypole is a way of going from a URL to a method
+call to some output. If you have a URL like C</product/order/12>,
+Maypole is a way of having it load up product number 12, call an
+C<order> method, and produce a page about what it's just done. The
+reason Maypole is such a big deal is because it does all this for you.
+You no longer have to care about your web server. You hardly have to
+care about your database. You don't have to care about templating
+modules, parsing CGI parameters, or anything else. You only need to care
+about business logic, and the business logic in this instance is how you
+C<order> a product, and what you need to display about it once you've
+done so. This is what programming should be: only caring about the work
+that distinguishes one program from another.
+
+It does this using a technique called MVC for web applications.
+
+=head2 What is MVC for web applications?
+
+Maypole was originally called C<Apache::MVC>, reflecting its basis in
+the Model-View-Controller design pattern. (I had to change it firstly
+because Maypole isn't tied to Apache, and secondly because
+C<Apache::MVC> is a really dull name.) It's the same design pattern that
+forms the foundation of similar projects in other languages, such as
+Java's Struts framework.
+
+This design pattern is found primarily in graphical applications; the
+idea is that you have a Model class which represents and manipulates
+your data, a View class which is responsible for displaying that data to
+the user, and a Controller class which controls the other classes in
+response to events triggered by the user. This analogy doesn't
+correspond precisely to a web-based application, but we can take an
+important principle from it. As Andy Wardley explains:
+
+    What the MVC-for-the-web crowd are really trying to achieve is a clear
+    separation of concerns.  Put your database code in one place, your 
+    application code in another, your presentation code in a third place.  
+    That way, you can chop and change different elements at will,
+    hopefully without affecting the other parts (depending on how well your
+    concerns are separated, of course).  This is common sense and good practice.
+    MVC achieves this separation of concerns as a by-product of clearly 
+    separating inputs (controls) and outputs (views). 
+
+This is what Maypole does. It has a number of database drivers, a number
+of front-end drivers and a number of templating presentation drivers.
+In common cases, Maypole provides precisely what you need for all of
+these areas, and you get to concentrate on writing just the business
+logic of your application. This is one of the reasons why Maypole lets
+you develop so rapidly: because most of the time, you don't need to do
+any development at all.
+
+=head2 Installing Maypole
+
+The first thing you're going to need to do to get Maypole running is to
+install it. Maypole needs an absolute shedload of Perl modules from CPAN
+to do its job. I am unrepentant about this. Maypole does a lot of work,
+so that you don't have to. This is called code re-use, and if we're
+serious about code re-use, then Maypole should be re-using as much code
+as possible in terms of Perl modules. In another sense, this gives the
+impression that Maypole doesn't actually do all that much itself,
+because all it's doing is gluing together already-existing code. Well,
+welcome to code re-use.
+
+The downside of code re-use is, of course, that you then have to install
+a shedload of Perl modules from CPAN. If you're using OpenBSD or
+FreeBSD, the wonderful ports system will be your friend. There's a
+Maypole port in C<p5-Maypole>. Just type C<make install>.
+
+Debian users, hang in there. There's a package coming.
+
+For other Unices, the L<CPANPLUS> or C<CPAN> modules will help with
+this. If you don't have C<CPANPLUS> installed, my recommendation is to
+use C<perl -MCPAN -e install CPANPLUS> to install it and then throw
+C<CPAN.pm> away. In any case, one of these two should get all that
+Maypole needs:
+
+    % perl -MCPANPLUS -e 'install Maypole'
+    % perl -MCPAN -e 'install Maypole'
+
+I don't know if Maypole works on Windows. I'm not sure I care.
+
+You're also going to need a database server and a web server. For
+databases, I recommend SQLite (if you install the C<DBD::SQLite> module,
+you get the SQLite library for free) for prototyping and mysql for
+production; heavier duty users should use Postgresql or Oracle - Maypole
+should be happy with them all. Maypole is happiest when running under
+Apache C<mod_perl>, with the C<Apache::Request> module installed, but as
+I said, it is a blank slate, and everything is customizable. There is a
+C<CGI::Maypole> frontend available to run as a standalone CGI script.
+
 =head2 The Beer Database example
 
 Throughout this manual, we're going to be referring back to a particular
@@ -153,7 +257,7 @@ of this manual will be about how to do that.
 
 In order to do that, we need to look at what Maypole's actually doing.
 
-XXX
+As mentioned, Maypole is XXX
 
 =head2 The model class
 
index 4c4093fa3c31e93f0e367672a63e4f3754c08e7e..033d9ce1acb8e10dc9f2d52a49c257b71757bc7b 100644 (file)
@@ -181,7 +181,50 @@ Maypole's default templates will use this information to display, for
 instance, a list of a brewery's beers on the brewery view page.
 
 This is the complete beer database application; Maypole's default templates
-and the actions in the view class do the rest. But what if we want to a
+and the actions in the view class do the rest. But what if we want to do a
 little more. How would we begin to extend this application?
 
 =head2 The hard way
+
+Maypole was written because I don't like writing more Perl code than is
+necessary. I also don't like writing HTML. In fact, I don't really get
+on this whole computer thing, to be honest. But we'll start by ways that
+we can customize the beer application simply by adding methods or
+changing properties of the Perl driver code.
+
+The first thing we ought to look into is the names of the columns; most
+of them are fine, but that "Abv" column stands out. I'd rather that was
+"A.B.V.". Maypole uses the C<column_names> method to map between the
+names of the columns in the database to the names it displays in the
+default templates. This is provided by C<Maypole::Model::Base>, and
+normally, it does a pretty good job; it turns C<model_number> into
+"Model Number", for instance, but there was no way it could guess that
+C<abv> was an abbreviation. Since it returns a hash, the easiest way
+to correct it is to construct a hash consisting of the bits it got
+right, and then override the bits it got wrong:
+
+    package BeerDB::Beer;
+    sub column_names { 
+        (shift->SUPER::column_names(), abv => "A.B.V.")
+    }
+
+Similarly, the order of columns is a bit wonky. We can fix this by
+overriding the C<display_columns> method; this is also a good way to
+hide away any columns we don't want to have displayed, in the same way
+as declaring the C<display_tables> configuration parameter let us hide
+away tables we weren't using:
+
+    sub display_columns { 
+        ("name", "brewery", "style", "price", "score", "abv", "notes")
+    }
+
+Hey, have you noticed that we haven't done anything with the
+beers/handpumps/pubs thing yet? Good, I was hoping that you hadn't.
+Ayway, this is because Maypole can't tell easily that a C<BeerDB::Beer>
+object can call C<pubs> to get a list of pubs. Not yet, at least; we're
+working on it. In the interim, we can explicitly tell Maypole which
+accessors are related to the C<BeerDB::Beer> class like so:
+
+    sub related { "pubs" }
+
+Now when we view a beer, we'll have a list of the pubs that it's on at.
index 1505d361d9a014b2f0c058e4e9c3f64b7676fb0e..fc51d1ba854ea87900250ce5a6f90d83bf96e602 100644 (file)
@@ -40,9 +40,70 @@ You can also use the C<Maypole::CLI> module programatically to create
 test suites for your application. See the Maypole tests themselves or
 the documentation to C<Maypole::CLI> for examples of this.
 
-=head3 Changing how params are parsed
+=head3 Changing how URLs are parsed
+
+You don't like the way Maypole URLs look, and want something that either
+fits in with the rest of your site or hides the internal workings of the
+system.
+
+C<Solution>: So far we've been using the C</table/action/id/args> form
+of a URL as though it was "the Maypole way"; well, there is no Maypole
+way. Maypole is just a framework and absolutely everything about it is 
+overridable. 
+
+If we want to provide our own URL handling, the method to override in
+the driver class is C<parse_path>. This is responsible for taking
+C<$r-E<gt>{path}> and filling the C<table>, C<action> and C<args> slots
+of the request object. Normally it does this just by splitting the path
+on C</>s, but you can do it any way you want, including getting the
+information from C<POST> form parameters or session variables. 
+
+For instance, suppose we want our URLs to be of the form
+C<ProductDisplay.html?id=123>, we could provide a C<parse_path> method
+like so:
+
+    sub parse_path {
+        my $r = shift;
+        $r->{path} ||= "ProductList.html";
+        ($r->{table}, $r->{action}) = 
+            ($r->{path} =~ /^(.*?)([A-Z]\w+)\.html/);
+        $r->{table}  = lc $r->{table};
+        $r->{action} = lc $r->{action};
+        my %query = $r->{ar}->args;
+        $self->{args} = [ $query{id} ];
+    }
 
-=head3 REST
+This takes the path, which already has the query parameters stripped off
+and parsed, and finds the table and action portions of the filename,
+lower-cases them, and then grabs the C<id> from the query. Later methods
+will confirm whether or not these tables and actions exist.
+
+See L<BuySpy.pod> for another example of custom URL processing.
+
+=head3 Maypole for mobile devices
+
+You want Maypole to use different templates to display on particular
+browsers.
+
+B<Solution>: There are several ways to do this, but here's the neatest
+we've found. Maypole chooses where to get its templates either by
+looking at the C<template_root> config parameter or, if this is not
+given, calling the C<get_template_root> method to ask the front-end to
+try to work it out. We can give the front-end a little bit of help, by
+putting this method in our driver class:
+
+    sub get_template_root {
+        my $r = shift;
+        my $browser = $r->{ar}->headers_in->get('User-Agent');
+        if ($browser =~ /mobile|palm|nokia/i) {
+            "/home/myapp/templates/mobile";
+        } else {
+            "/home/myapp/templates/desktop";
+        }
+    }
+
+(Maybe there's a better way to detect a mobile browser, but you get the
+idea.)
 
 =head2 Content display hacks
 
@@ -99,10 +160,6 @@ a user's page.
 This hack is so common that it's expected that there'll be a neater
 way of doing this in the future.
 
-=head3 Maypole for mobile devices
-
-XXX
-
 =head3 XSLT
 
 Here's a hack I've used a number of times. You want to store structured
@@ -194,6 +251,47 @@ itself; if your file is stored in the filesystem, but you have a file
 name or some other pointer in the database, you can still arrange for
 the data to be fetched and inserted into C<$r-E<gt>{output}>.
 
+=head3 REST
+
+You want to provide a programmatic interface to your Maypole site.
+
+B<Solution>: The best way to do this is with C<REST>, which uses a
+descriptive URL to encode the request. For instance, in L<Flox.pod> we
+describe a social networking system. One neat thing you can do with
+social networks is to use them for reputation tracking, and we can use
+that information for spam detection. So if a message arrives from
+C<person@someco.com>, we want to know if they're in our network of
+friends or not and mark the message appropriately. We'll do this by
+having a web agent (say, L<WWW::Mechanize> or L<LWP::UserAgent>) request
+a URL of the form
+C<http://flox.simon-cozens.org/user/relationship_by_email/person%40someco.com>.
+Naturally, they'll need to present the appropriate cookie just like a
+normal browser, but that's a solved problem. We're just interested in
+the REST request.
+
+The request will return a single integer status code: 0 if they're not
+in the system at all, 1 if they're in the system, and 2 if they're our
+friend.
+
+All we need to do to implement this is provide the C<relationship_by_email>
+action, and use it to fill in the output in the same way as we did when
+displaying a picture. Since C<person%40someco.com> is not the ID of a
+row in the user table, it will appear in the C<args> array:
+
+    use URI::Escape;
+    sub relationship_by_email :Exported {
+        my ($self, $r) = @_;
+        my $email = uri_unescape($r->{args}[0]);
+        $r->{content_type} = "text/plain";
+        my $user;
+        unless (($user) = Flox::User->search(email => $email)) {
+            $r->{content} = "0\n"; return;
+        }
+
+        if ($r->{user}->is_friend($user)) { $r->{content} = "2\n"; return; };
+        $r->{content} = "1\n"; return;
+    }
+
 =head3 Component-based Pages
 
 You're designing something like a portal site which has a number of