package Kelp::Module; use Kelp::Base; use Carp; our @CARP_NOT = qw(Kelp); attr -app => sub { die "app is required" }; sub new { my $self = shift->SUPER::new(@_); $self->app; return $self; } # Override this to register items sub build { my ($self, %args) = @_; } sub register { my ($self, %items) = @_; while (my ($name, $item) = each(%items)) { no strict 'refs'; no warnings 'redefine'; my $app = ref $self->app; my $glob = "${app}::$name"; # Manually check if the glob is being redefined if (!$ENV{KELP_REDEFINE} && $self->app->can($name)) { croak "Redefining of $glob not allowed"; } if (ref $item eq 'CODE') { *{$glob} = $item; } else { $self->app->{$name} = $item; *{$glob} = sub { $_[0]->{$name} } } } } 1; __END__ =pod =head1 NAME Kelp::Module - Base class for Kelp modules =head1 SYNOPSIS package Kelp::Module::MyModule; use parent 'Kelp::Module'; sub build { my ( $self, %args ) = @_; $self->register( greet => sub { print "Hi there." } ); } =head1 DESCRIPTION Provides the base class for creating Kelp modules. Creating a Kelp module means extending this class and overriding the C<build> method. Kelp modules usually C<register> a new method into the web application. =head2 Registering methods Modules use the L</register> method to register new methods into the underlying web application. All the registrations are done in the L</build> subroutine. All types of values can be registered and then accessed as a read-only attribute from the web app. The simplest thing you can register is a scalar value: First... # lib/Kelp/Module/Month.pm package Kelp::Module::Month; use Kelp::Base 'Kelp::Module'; sub build { my ( $self, %args ) = @_; $self->register( month => 'October' ); } Then ... # lib/MyApp.pm package MyApp; use parent 'Kelp'; sub build { my $self = shift; $self->load_module("Month"); } sub is_it_october_yet { my $self = shift; if ( $self->month eq 'October' ) { return "It is October"; } return "Not yet."; } The above example doesn't do anything meaningful, but it's a good way to show how to create and use Kelp modules. Pay attention to the next example, as it will show you how to register an anonymous subroutine: package Kelp::Module::Date; use Kelp::Base 'Kelp::Module'; use DateTime; sub build { my ( $self, %args ) = @_; $self->register( date => sub { return DateTime->from_epoch( epoch => time ); } ); } Now, each time you use C<$self-E<gt>date> in the web application, you will create a new C<DateTime> object for the current time. It is more practical to register an already created object. Consider this example, which uses C<Redis>, initializes an instance of it and registers it as a method in the web app: package Kelp::Module::Redis; use Kelp::Base 'Kelp::Module'; use Redis; sub build { my ( $self, %args ) = @_; my $redis = Redis->new(%args); $self->register( redis => $redis ); } =head2 Passing arguments to your module The arguments for all modules are taken from the configuration. If you want to pass arguments for your C<Redis> module (example above), you will have to have a structure in your config, similar to this: Example of C<conf/myapp.pl>: { # Load module Redis on start modules => ['Redis'], modules_init => { Redis => { server => '192.168.0.1:6379', encoding => 'UTF-8', password => 'boo' } } }; The hash specified by C<Redis> will be sent as C<%args> in the C<build> method of the module. =head2 Loading modules that live outside of the Kelp::Module namespace Kelp will automatically prefix all modules with C<Kelp::Module>, so a module name C<Redis> should live in C<Kelp::Module::Redis>. To use fully qualified modules that live outside of the C<Kelp::Module> namespace, prefix the name with a plus sign. { # Load a module that lives outside of Kelp::Module modules => ["+Fully::Qualified::Name"], modules_init => { "+Fully::Qualified::Name" => {...} } }; =head1 METHODS =head2 build C<build( %args )> Each module must override this one in order to register new methods. The C<%args> hash will be taken from the configuration. =head2 register C<register( %items )> Registers one or many methods into the web application. $self->register( json => JSON->new, yaml => YAML->new ); =cut