| Jifty documentation | Contained in the Jifty distribution. |
Jifty::ClassLoader - Loads the application classes
Jifty::ClassLoader loads all of the application's model and action
classes, generating classes on the fly for Collections of pre-existing
models.
Returns a new ClassLoader object. Doing this installs a hook into
@INC that allows Jifty::ClassLoader to dynamically create
needed classes if they do not exist already. This works because if
use/require encounters a blessed reference in @INC, it will
invoke the INC method with the name of the module it is searching
for on the reference.
Takes one mandatory argument, base, which should be the the
application's or a plugin's base path; all of the classes under this will be
automatically loaded.
Jifty::ClassLoader objects are singletons per base. If you call 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.
The hook that is called when a module has been require'd that
cannot be found on disk. The following stub classes are
auto-generated the class loader.
Here the "Application" indicates the name of the application the class
loader is being applied to. However, this really just refers to the base
argument passed to the constructor, so it could refer to a plugin class
or just about anything else.
An empty application base class is created that doesn't provide any methods or inherit from anything.
An empty mixin class for all actions in your application. Should not inherit from Jifty::Action.
If Application::Model::Something is a valid model class and Verb is one of "Create", "Search", "Update", "Execute", or "Delete", then it creates a subclass of Application::Action::Record::Verb Models can control which actions are generated by overriding autogenerate_action in Jifty::Record. See also is_private in Jifty::Record and is_protected in Jifty::Record.
The class loader will search for a plugin Plugin such that Plugin::Action::Something exists. It will then create an empty class named Application::Action::Something that descends from Plugin::Action::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.
An empty class that descends from the matching Jifty class, Jifty::Action::Record::Something. This is generally used to build application-specific descendants of Jifty::Action::Record::Create, Jifty::Action::Record::Execute, Jifty::Action::Record::Search, Jifty::Action::Record::Update, or Jifty::Action::Record::Delete.
An empty class that descends from Jifty::Bootstrap.
An empty class that descends from Jifty::Collection is created.
An empty class that descends from Jifty::CurrentUser.
An empty class that descends from Jifty::Dispatcher.
An empty class that descends from Jifty::Event is created.
An empty class that descents from Jifty::Event::Model is created.
If Application::Model::Something is a valid model class, then it creates an empty descendant of Application::Event::Model with the record_class set to Application::Model::Something.
An empty class that descends from Jifty::Handle is created.
If Plugin::Model::Something exists and is a model class, then it creates a subclass of it named Application::Model::Something for the local application.
This allows an application to customize a model provided by a subclass (or choose not to). Plugins should be written to use the application's version.
If Application::Model::Something is a valid model class, then
it creates a subclass of Jifty::Collection whose record_class is
Application::Model::Something.
An empty class that descends from Jifty::Notification.
The class loader will search for a plugin Plugin such that Plugin::Notification::Something exists. It will then create an empty class named Application::Notification::Something that descends from Plugin::Notification::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.
An empty class that descends from Jifty::Record is created.
An empty class that descends from Jifty::Upgrade.
An empty class that descends from Jifty::View::Declare.
A helper method; takes CODE as a string and returns an open filehandle containing that CODE.
Loads all of the application's Actions and Models. It additionally
require's all Collections and Create/Update actions for each Model
base class -- which will auto-create them using the above code if they
do not exist on disk.
Jifty supports model classes that aren't files on disk but instead records in your database. It's a little bit mind bending, but basically, you can build an application entirely out of the database without ever writing a line of code(*).
* As of early 2007, this forward looking statement is mostly a lie. But we're working on it.
This method finds all database-backed models and instantiates jifty classes for them it returns a list of class names of the models it created.
Load up $appname::View, the view class for the application.
Accessor to the list of models this application has loaded.
In scalar context, returns a mutable array reference; in list context, return the content of the array.
ClassLoaders stringify as Jifty::ClassLoader(base class name)
When the ClassLoader gets garbage-collected, its entry in @INC needs to be removed.
Returns true if the package was autogenerated by a classloader.
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:
package MyApp::Dispatcher; use Jifty::Dispatcher -base;
If you want to add some application specific behaviour to a model's collection class, say for the User model, create UserCollection.pm in your applications Model directory.
package MyApp::Model::UserCollection; use base 'MyApp::Collection';
Jifty and just about every other class that this provides an empty override for.
Jifty is Copyright 2005-2010 Best Practical Solutions, LLC. Jifty is distributed under the same terms as Perl itself.
| Jifty documentation | Contained in the Jifty distribution. |
use warnings; use strict; package Jifty::ClassLoader; our %AUTOGENERATED; use overload '""' => \&stringify, fallback => 1;
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; }
# This subroutine's name is fully qualified, as perl will ignore a 'sub INC' sub Jifty::ClassLoader::INC { my ( $self, $module ) = @_; my $base = $self->{base}; return undef unless ( $module and $base ); # Canonicalize $module to :: style rather than / and .pm style; $module =~ s/.pm$//; $module =~ s{/}{::}g; # The quick check. We only want to handle things for our app return undef unless $module =~ /^$base/; # If the module is the same as the base, build the application class if ( $module =~ /^(?:$base)$/ ) { return $self->return_class( "package " . $base . ";\n"); } # Handle most of the standard App::Class ISA Jifty::Class elsif ( $module =~ /^(?:$base)::(Record|Collection|Notification| Dispatcher|Bootstrap|Upgrade|CurrentUser| Handle|Event|Event::Model| Action::Record::\w+)$/x ) { $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use base qw/Jifty::$1/; \n" ); } # Autogenerate empty Action mixin elsif ( $module =~ /^(?:$base)::Action$/) { $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" ); } # Autogenerate an empty View if none is defined elsif ( $module =~ /^(?:$base)::View$/ ) { $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use Jifty::View::Declare -base;\n" ); } # Autogenerate the Collection class for a Model elsif ( $module =~ /^(?:$base)::Model::([^\.]+)Collection$/ ) { $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use base qw/@{[$base]}::Collection/;\n" . "sub record_class { '@{[$base]}::Model::$1' }\n" ); } # Autogenerate the the event class for model changes elsif ( $module =~ /^(?:$base)::Event::Model::([^\.]+)$/ ) { # Determine the model class and load it my $modelclass = $base . "::Model::" . $1; Jifty::Util->require($modelclass); # Don't generate an event unless it really is a model return undef unless eval { $modelclass->isa('Jifty::Record') }; $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use base qw/${base}::Event::Model/;\n" . "sub record_class { '$modelclass' };\n" ); } # Autogenerate the record actions for a model elsif ( $module =~ /^(?:$base)::Action:: (Create|Update|Delete|Search|Execute)([^\.]+)$/x ) { my $action = $1; my $model = $2; # Determine the model class and load it my $modelclass = $base . "::Model::" . $model; Jifty::Util->_require( module => $modelclass, quiet => 1); # Don't generate the action unless it really is a model if ( eval { $modelclass->isa('Jifty::Record') } ) { if ($modelclass->autogenerate_action($action)) { # Skip autogenerated models; that is, those that are overridden # by plugins, the special case below should take care of it if (!$self->autogenerated($modelclass)) { $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use base qw/$base\::Action::Record::$action/;\n" . "sub record_class { '$modelclass' };\n" ); } } } } # 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 model 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, models, 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|Execute)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|Execute)Thing for # Plugin::Model::Thing instead. # Requesting an application override of a plugin action or notification? if ( $module =~ /^(?:$base)::(Action|Model|Notification)::(.*)$/x and not grep {$_ eq $base} map {ref} Jifty->plugins ) { my $type = $1; my $item = $2; # 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; # Found it! if (Jifty::Util->try_to_require($class) ) { # Models need to load their other stuff, but to prevent deep # recursion, this must happen after the class is compiled. my $module_suffix = ''; $module_suffix = "Jifty->class_loader" . "->_require_model_related_classes(" . "'$module');\n" if $type eq 'Model'; # Generate the empty stub $AUTOGENERATED{$module} = 1; return $self->return_class( "package $module;\n" . "use base qw/$class/;\n" . $module_suffix ); } } } # Didn't find a match return undef; }
sub return_class { 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; }
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"); # 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')], require => 0, except => qr/\.#/, inner => 0 ); # Construct the list of models for the application for later reference my %models; for my $p ($self->plugins) { Jifty::Util->require($p); $models{$p} = 1 if $p =~ m/^($base)::Model::(.*)$/ and not $p =~ m/Collection(?:$||\:\:)/; } $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::(\w*)/; Jifty::Util->require($full . "Collection"); Jifty::Util->require($base . "::Action::" . $_ . $short) for grep {$full->autogenerate_action($_)} qw/Create Update Delete Search Execute/; }
# 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; require Jifty::Model::ModelClassCollection; require Jifty::Model::ModelClass; my $models = Jifty::Model::ModelClassCollection->new(current_user => Jifty->app_class('CurrentUser')->superuser); $models->unlimit(); while (my $model = $models->next) { $model->instantiate(); $self->_require_model_related_classes($model->qualified_class); } }
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"); }
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}; }
sub stringify { my $self = shift; return "Jifty::ClassLoader(@{[$self->{base}]})"; }
# The entries in @INC end up having SvTYPE == SVt_RV, but SvRV(sv) == # 0x0 and !SvROK(sv) (!?) This may be something that perl should cope # with more cleanly. # # We call this explictly in an END block in Jifty.pm, because # otherwise the DESTROY block gets called *after* there's already a # bogus entry in @INC # This bug manifests itself as warnings that look like this: # Use of uninitialized value in require at /tmp/7730 line 9 during global destruction. sub DESTROY { my $self = shift; @INC = grep {defined $_ and not (ref $_ and Scalar::Util::refaddr($_) ne Scalar::Util::refaddr($self))} @INC; }
sub autogenerated { my $class = shift; my $classname = shift; return $AUTOGENERATED{$classname}; }
1;