[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