[Jifty-commit] r3885 - in jifty/trunk: lib/Jifty

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Mon Aug 13 21:59:58 EDT 2007


Author: sterling
Date: Mon Aug 13 21:59:57 2007
New Revision: 3885

Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/lib/Jifty/ClassLoader.pm

Log:
 r8620 at dynpc145:  andrew | 2007-08-13 20:51:09 -0500
 Major Pod improvement to the class loader, many more helpful code comments, and some tidying for the class loader. Phew.


Modified: jifty/trunk/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/trunk/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/trunk/lib/Jifty/ClassLoader.pm	Mon Aug 13 21:59:57 2007
@@ -23,18 +23,22 @@
 for on the reference.
 
 Takes one mandatory argument, C<base>, which should be the the
-application's base path; all of the classes under this will be
+application's or a plugin's base path; all of the classes under this will be
 automatically loaded.
 
+L<Jifty::ClassLoader> objects are singletons per C<base>. If you call C<new> and a class loader for the given base has already been initialized, this will return a reference to that object rather than creating a new one.
+
 =cut
 
 sub new {
     my $class = shift;
     my %args = @_;
 
+    # Check to make sure this classloader hasn't been built yet and stop if so
     my @exist = grep {ref $_ eq $class and $_->{base} eq $args{base}} @INC;
     return $exist[0] if @exist;
 
+    # It's a new one, build it
     my $self = bless {%args}, $class;
     push @INC, $self;
     return $self;
@@ -44,7 +48,9 @@
 
 The hook that is called when a module has been C<require>'d that
 cannot be found on disk.  The following stub classes are
-auto-generated:
+auto-generated the class loader. 
+
+Here the "I<Application>" indicates the name of the application the class loader is being applied to. However, this really just refers to the C<base> argument passed to the constructor, so it could refer to a plugin class or just about anything else.
 
 =over
 
@@ -53,55 +59,83 @@
 An empty application base class is created that doen't provide any
 methods or inherit from anything.
 
-=item I<Application>::Record
+=item I<Application>::Action
 
-An empty class that descends from L<Jifty::Record> is created.
+An empty class that descends from L<Jifty::Action>.
 
-=item I<Application>::Event
+=item I<Application>::Action::I<[Verb]>I<[Something]>
 
-An empty class that descends from L<Jifty::Event> is created.
+If I<Application>::Model::I<Something> is a valid model class and I<Verb> is one of "Create", "Search", "Update", or "Delete", then it creates a subclass of I<Application>::Action::Record::I<Verb>
+
+=item I<Application>::Action::I<Something>
+
+The class loader will search for a plugin I<Plugin> such that I<Plugin>::Action::I<Something> exists. It will then create an empty class named I<Application>::Action::I<Something> that descends from I<Plugin>::Action::I<Something>.
+
+This means that a plugin may be written to allow the application to override the default implementation used by the plugin as long as the plugin uses the application version of the class.
+
+=item I<Application>::Action::Record::I<Something>
+
+An empty class that descends from the matching Jifty class, Jifty::Action::Record::I<Something>. This is generally used to build application-specific descendants of L<Jifty::Action::Record::Create>, L<Jifty::Action::Record::Search>, L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete>.
+
+=item I<Application>::Bootstrap
+
+An empty class that descends from L<Jifty::Bootstrap>.
 
 =item I<Application>::Collection
 
 An empty class that descends from L<Jifty::Collection> is created.
 
-=item I<Application>::Notification
+=item I<Application>::CurrentUser
 
-An empty class that descends from L<Jifty::Notification>.
+An empty class that descends from L<Jifty::CurrentUser>.
 
 =item I<Application>::Dispatcher
 
 An empty class that descends from L<Jifty::Dispatcher>.
 
-=item I<Application>::Handle
+=item I<Application>::Event
 
-An empty class that descends from L<Jifty::Handle> is created.
+An empty class that descends from L<Jifty::Event> is created.
 
-=item I<Application>::Bootstrap
+=item I<Application>::Event::Model
 
-An empty class that descends from L<Jifty::Bootstrap>.
+An empty class that descents from L<Jifty::Event::Model> is created.
 
-=item I<Application>::Upgrade
+=item I<Application>::Event::Model::I<Something>
 
-An empty class that descends from L<Jifty::Upgrade>.
+If I<Application>::Model::I<Something> is a valid model class, then it creates an empty descendant of I<Application>::Event::Model with the C<record_class> set to I<Application>::Model::I<Something>.
 
-=item I<Application>::CurrentUser
+=item I<Application>::Handle
 
-An empty class that descends from L<Jifty::CurrentUser>.
+An empty class that descends from L<Jifty::Handle> is created.
 
-=item I<Application>::Model::I<Anything>Collection
+=item I<Application>::Model::I<Something>Collection
 
 If C<I<Application>::Model::I<Something>> is a valid model class, then
 it creates a subclass of L<Jifty::Collection> whose C<record_class> is
 C<I<Application>::Model::I<Something>>.
 
-=item I<Application>::Action::(Create or Update or Delete or Search)I<Anything>
+=item I<Application>::Notification
 
-If C<I<Application>::Model::I<Something>> is a valid model class, then it
-creates a subclass of L<Jifty::Action::Record::Create>,
-L<Jifty::Action::Record::Update>, L<Jifty::Action::Record::Delete> or
-L<Jifty::Action::Record::Search> whose I<record_class> is
-C<I<Application>::Model::I<Something>>.
+An empty class that descends from L<Jifty::Notification>.
+
+=item I<Application>::Notification::I<Something>
+
+The class loader will search for a plugin I<Plugin> such that I<Plugin>::Notification::I<Something> exists. It will then create an empty class named I<Application>::Notification::I<Something> that descends from I<Plugin>::Notification::I<Something>.
+
+This allows an application to customize the email notification sent out by a plugin as long as the plugin defers to the application version of the class.
+
+=item I<Application>::Record
+
+An empty class that descends from L<Jifty::Record> is created.
+
+=item I<Application>::Upgrade
+
+An empty class that descends from L<Jifty::Upgrade>.
+
+=item I<Application>::View
+
+An empty class that descends from L<Jifty::View::Declare>.
 
 =back
 
@@ -192,28 +226,50 @@
 
     }
 
-    # This is if, not elsif because we might have $base::Action::Deleteblah
-    # that matches that last elsif clause but loses on the eval.
+    # This is a little hard to grok, so pay attention. This next if checks to
+    # see if the requested class belongs to an application (i.e., this class
+    # loader does not belong to a plugin). If so, it will attempt to create an
+    # application override of a plugin class, if the plugin provides the same
+    # type of notification or action.
+    #
+    # This allows the application to customize what happens on a plugin action
+    # or customize the email notification sent by a plugin. 
+    #
+    # However, this depends on the plugin being well-behaved and always using
+    # the application version of actions and notifications rather than trying
+    # to use the plugin class directly.
+    #
+    # Of course, if the class loader finds such a case, then the application
+    # has not chosen to override it and we're generating the empty stub to take
+    # it's place.
+
+    # N.B. This is if and not elsif on purpose. If the class name requested is
+    # App::Action::(Create|Update|Search|Delete)Thing, but there is no such
+    # model as App::Model::Thing, we may be trying to create a sub-class of
+    # Plugin::Action::(Create|Update|Search|Delete)Thing for
+    # Plugin::Model::Thing instead.
+    
+    # Requesting an application override of a plugin action or notification?
     if ( $module =~ /^(?:$base)::(Action|Notification)::(.*)$/x and not grep {$_ eq $base} map {ref} Jifty->plugins ) {
         my $type = $1;
         my $item = $2;
-        # If we don't have the action in our own app, let's try the plugins
-        # the app has loaded.
+
+        # Find a plugin with a matching action or notification
         foreach my $plugin (map {ref} Jifty->plugins) {
             next if ($plugin eq $base);
             my $class = $plugin."::".$type."::".$item;
-            if (Jifty::Util->try_to_require($class) ) {
-        return $self->return_class(
-                  "package $module;\n"
-                . "use base qw/$class/;\n"
-                . "sub _autogenerated { 1 };\n"
-            );
-
 
+            # Found it! Generate the empty stub.
+            if (Jifty::Util->try_to_require($class) ) {
+                return $self->return_class(
+                        "package $module;\n"
+                        . "use base qw/$class/;\n"
+                        . "sub _autogenerated { 1 };\n"
+                    );
             }
         }
-
     }
+
     # Didn't find a match
     return undef;
 }
@@ -229,11 +285,12 @@
     my $self = shift;
     my $content = shift;
 
+    # ALWAYS use warnings; use strict!!!
     $content = "use warnings; use strict; ". $content  . "\n1;";
 
+    # Magically turn the text into a file handle
     open my $fh, '<', \$content;
     return $fh;
-
 }
 
 =head2 require
@@ -247,16 +304,22 @@
 
 sub require {
     my $self = shift;
-
     my $base = $self->{base};
+
+    # XXX It would be nice to have a comment here or somewhere in here
+    # indicating when it's possible for a class loader to be missing it's base.
+    # This is a consistent check in the class loader, but I don't know of an
+    # example where this would be the case. -- Sterling
+
     # if we don't even have an application class, this trick will not work
     return unless ($base);
+
+    # Always require the base and the base current user first
     Jifty::Util->require($base);
     Jifty::Util->require($base."::CurrentUser");
 
-    my %models;
-
-
+    # Use Module::Pluggable to help locate our models, actions, notifications,
+    # and events
     Jifty::Module::Pluggable->import(
         # $base goes last so we pull in the view class AFTER the model classes
         search_path => [map { $base . "::" . $_ } ('Model', 'Action', 'Notification', 'Event')],
@@ -264,23 +327,28 @@
         except  => qr/\.#/,
         inner   => 0
     );
+    
+    # Construct the list of models for the application for later reference
+    my %models;
     $models{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins;
     $self->models(sort keys %models);
+
+    # Load all those models and model-related actions, notifications, and events
     for my $full ($self->models) {
         $self->_require_model_related_classes($full);
     }
-
 }
 
+# This class helps Jifty::ClassLoader::require() load each model, the model's
+# collection and the model's create, update, delete, and search actions.
 sub _require_model_related_classes {
     my $self = shift;
     my $full = shift;
     my $base = $self->{base};
-        my($short) = $full =~ /::Model::(.*)/;
-        Jifty::Util->require($full . "Collection");
-        Jifty::Util->require($base . "::Action::" . $_ . $short)
-            for qw/Create Update Delete Search/;
-
+    my($short) = $full =~ /::Model::(.*)/;
+    Jifty::Util->require($full . "Collection");
+    Jifty::Util->require($base . "::Action::" . $_ . $short)
+        for qw/Create Update Delete Search/;
 }
 
 
@@ -299,6 +367,10 @@
 
 =cut
 
+# XXX TODO FIXME Holy crap! This is in the trunk! See the virtual-models branch
+# of Jifty if you really want to see this in action (unless it's finally been
+# merged intot he trunk), which isn't the case as of August 13, 2007. 
+# -- Sterling
 sub require_classes_from_database {
     my $self = shift;
     my @instantiated;
@@ -321,8 +393,8 @@
 
 sub require_views {
     my $self = shift;
-
     my $base = $self->{base};
+
     # if we don't even have an application class, this trick will not work
     return unless ($base);
     Jifty::Util->require($base."::View");
@@ -339,9 +411,13 @@
 
 sub models {
     my $self = shift;
+
+    # If we have args, update the list of models
     if (@_) {
         $self->{models} = ref($_[0]) ? $_[0] : \@_;
     }
+
+    # DWIM: return an array if they want a list, return an arrayref otherwise
     wantarray ? @{ $self->{models} ||= [] } : $self->{models};
 }
 
@@ -364,17 +440,17 @@
 
 # Use of uninitialized value in require at /tmp/7730 line 9 during global destruction.
 
-
 sub DESTROY {
     my $self = shift;
     @INC = grep {defined $_ and $_ ne $self} @INC;
 }
 
-=head1 Writing your own classes
+=head1 WRITING YOUR OWN CLASSES
 
-If you require more functionality than is provided by the classes
-created by ClassLoader then you should create a class with the
-appropriate name and add your extra logic to it.
+If you require more functionality than is provided by the classes created by
+ClassLoader (which you'll almost certainly need to do if you want an
+application that does more than display a pretty Pony) then you should create a
+class with the appropriate name and add your extra logic to it.
 
 For example you will almost certainly want to write your own
 dispatcher, so something like:
@@ -389,6 +465,15 @@
  package MyApp::Model::UserCollection;
  use base 'MyApp::Collection';
 
+=head1 SEE ALSO
+
+L<Jifty> and just about every other class that this provides an empty override for.
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
 =cut
 
 1;


More information about the Jifty-commit mailing list