Discussion:
[cgi-prototype-users] Creating mixins for CGI::Prototype
Andrew Gianni
2007-08-16 14:34:32 UTC
Permalink
I'd like to figure out how to get mixins up and running under
CGI::Prototype. I think I understand the technical aspects of
Class::Prototyped well enough to pull it off, but I have design questions
that I'd like feedback on. Assuming all goes well, I'd be happy to
contribute a CGI::Prototype::Mixin module to CPAN if I can get it that
generalized. Let me start with three scenarios I'd like to be able to
address and the issues I see:

1. We have at least two applications that have pages that allow users to
upload files. The page is pretty similar in both applications with only a
few application specific things, like the table it's using and an extra
field. What I'd like to do is create an abstract page class for the file
upload page and then implement it with concrete page classes within the
applications themselves, overriding necessary methods to customize. I have
actually done this in the past by not giving the abstract class a base class
and using multiple inheritance in the concrete class like so:

use base qw( Abstract::Page::Class My::App );

And that mostly works, but I'm knew that it could cause problems down the
line and after only a few months I found a scenario where it breaks: under
mod_perl, if I use the mixin page for more than one concrete page class in
my application and use Apache::Reload to reload the concrete page classes, I
get errors with whichever concrete page class is reloaded second. I don't
remember exactly what the error is, but I can look into it if anyone is
interested. Cracking plugability for this example is most important for me
because we actually have two more page types that we need to be able to do
this with and right now they're just duplicated in multiple projects.

2. I would like to create a pluggable diagnostics module that I could stick
in front of my app module to override core CGI::P methods at strategic
locations to capture performance data.

3. We currently have two different validation methodologies and I'm not sure
how soon we'll be picking one or the other to standardize on. I'd like to
create two separate mixin classes so I can choose the validation
functionality to use. I know I could make this work with something like a
Strategy pattern, defining two different validation classes with the same
interface and telling the app which one to use, but if I can get the first
two examples working, this could easily come along for the ride.

The basics of this from a technical perspective should be pretty straight
forward, right? I just need to take the mixin class and manipulate it's
parent and the parent of the class that I want to inherit from it, right?

The complicated part, and perhaps this doesn't have an easy answer, is what
it I want to insert more than one mixin? What if, for example,
CGI::Prototype::Hidden became a mixin, as Randal proposed some time ago (did
I understand that correctly?) and I also have an authentication mixin I'd
like to use. The problem is that, as a developer, I know the interfaces of
the two mixins and what they do, but I don't want to have to look at the
internals of each one to know if I need to put CGI::Prototype::Hidden
infront of CGI::Prototype::Mixin::Auth or vice versa. Is this actually a
connundrum or am I overanalyzing this? Any feedback or ideas would be
appreciated.

Andrew
--
Andrew Gianni - Lead Programmer Analyst
University at Buffalo, State University of New York
Computing and Information Technology / Administrative Computing Services
215 MFAC, Ellicott Complex, Buffalo, NY 14261-0026
716.645.5332 - AIM: andrewsgianni - http://www.newkenmore.com/agianni
Dotan Dimet
2007-08-16 16:44:45 UTC
Permalink
Hi Andrew,

CGI::Prototype as a framework is something of a bit of string lashed
around a set of power tools.

These tools can do anything you need, but the "framework" provides no
guidance about how to do it, beyond the "with this" inherent in
Randall's choice of tools.

Instead of 'use base', I use the Class::Prototyped ability to install
other classes into your object.


In my base class (CGI::Prototype::Hidden subclass), I've got a method
that installs my plugins:


sub install_mixins {

my ($self, @mixins) = @_;
my $prefix = $self->mixins_prefix . '::';
foreach my $pkg (@mixins) {
$self->reflect->addSlot ( [ "$pkg*", 'PARENT' ] => $prefix . $pkg );
my $plugin = $self->reflect->getSlot("$pkg*"); # the mixin we
just installed
}
}


The mixins_prefix is "configurable":

sub mixins_prefix { return "Miner::Utils" }


And I install my mixins in app_enter in the base class:


sub app_enter {
my ($self) = @_;
# connect to the database:
Foo::DBI::init($Config::Site::database_connect_string,
$Config::Site::schema, $Config::Site::schema);
# load all the Miner::Utils::* packages:
$self->install_mixins(qw(Links Text Cookie Exec Log Order TimeField
LOB ));
}


If I want to use a particular mixin only in one or more specific pages,
I call install_mixins in their control_enter or respond_enter methods.


My plugin/mixin classes (like Miner::Utils::Cookie, for example) are
CGI::Prototype::Hidden subclasses, although I could probably make them
Class::Prototyped subclasses, or independent modules.


The advantage of calling install_mixins in the app_enter is that things
like the CGI object and the database connection are already set up, so I
can install classes that mess about with things like the database
session, like Class::DBI::Plugin::DateFormat::Oracle for example.


Hope this helps,


- Dotan.
Post by Andrew Gianni
I'd like to figure out how to get mixins up and running under
CGI::Prototype. I think I understand the technical aspects of
Class::Prototyped well enough to pull it off, but I have design questions
that I'd like feedback on. Assuming all goes well, I'd be happy to
contribute a CGI::Prototype::Mixin module to CPAN if I can get it that
generalized. Let me start with three scenarios I'd like to be able to
1. We have at least two applications that have pages that allow users to
upload files. The page is pretty similar in both applications with only a
few application specific things, like the table it's using and an extra
field. What I'd like to do is create an abstract page class for the file
upload page and then implement it with concrete page classes within the
applications themselves, overriding necessary methods to customize. I have
actually done this in the past by not giving the abstract class a base class
use base qw( Abstract::Page::Class My::App );
And that mostly works, but I'm knew that it could cause problems down the
line and after only a few months I found a scenario where it breaks: under
mod_perl, if I use the mixin page for more than one concrete page class in
my application and use Apache::Reload to reload the concrete page classes, I
get errors with whichever concrete page class is reloaded second. I don't
remember exactly what the error is, but I can look into it if anyone is
interested. Cracking plugability for this example is most important for me
because we actually have two more page types that we need to be able to do
this with and right now they're just duplicated in multiple projects.
2. I would like to create a pluggable diagnostics module that I could stick
in front of my app module to override core CGI::P methods at strategic
locations to capture performance data.
3. We currently have two different validation methodologies and I'm not sure
how soon we'll be picking one or the other to standardize on. I'd like to
create two separate mixin classes so I can choose the validation
functionality to use. I know I could make this work with something like a
Strategy pattern, defining two different validation classes with the same
interface and telling the app which one to use, but if I can get the first
two examples working, this could easily come along for the ride.
The basics of this from a technical perspective should be pretty straight
forward, right? I just need to take the mixin class and manipulate it's
parent and the parent of the class that I want to inherit from it, right?
The complicated part, and perhaps this doesn't have an easy answer, is what
it I want to insert more than one mixin? What if, for example,
CGI::Prototype::Hidden became a mixin, as Randal proposed some time ago (did
I understand that correctly?) and I also have an authentication mixin I'd
like to use. The problem is that, as a developer, I know the interfaces of
the two mixins and what they do, but I don't want to have to look at the
internals of each one to know if I need to put CGI::Prototype::Hidden
infront of CGI::Prototype::Mixin::Auth or vice versa. Is this actually a
connundrum or am I overanalyzing this? Any feedback or ideas would be
appreciated.
Andrew
Andrew Gianni
2007-08-16 17:28:23 UTC
Permalink
Dotan, that looks like what I'm looking for and it's roughly what I had in
mind although I didn't realize that a Class::Prototyped class could have
multiple parents, which resolves the question of multiple mixins. How are
they evaluated for execution? For example, if you override render_enter in
Miner::Utils::Links and Miner::Utils::Text, will it just execute both of
their render_enter methods in sequence in the order you added their parent
slot in the foreach loop of install_mixins?

Andrew
Post by Dotan Dimet
Hi Andrew,
CGI::Prototype as a framework is something of a bit of string lashed
around a set of power tools.
These tools can do anything you need, but the "framework" provides no
guidance about how to do it, beyond the "with this" inherent in
Randall's choice of tools.
Instead of 'use base', I use the Class::Prototyped ability to install
other classes into your object.
In my base class (CGI::Prototype::Hidden subclass), I've got a method
sub install_mixins {
my $prefix = $self->mixins_prefix . '::';
$self->reflect->addSlot ( [ "$pkg*", 'PARENT' ] => $prefix . $pkg );
my $plugin = $self->reflect->getSlot("$pkg*"); # the mixin we
just installed
}
}
sub mixins_prefix { return "Miner::Utils" }
sub app_enter {
Foo::DBI::init($Config::Site::database_connect_string,
$Config::Site::schema, $Config::Site::schema);
$self->install_mixins(qw(Links Text Cookie Exec Log Order TimeField
LOB ));
}
If I want to use a particular mixin only in one or more specific pages,
I call install_mixins in their control_enter or respond_enter methods.
My plugin/mixin classes (like Miner::Utils::Cookie, for example) are
CGI::Prototype::Hidden subclasses, although I could probably make them
Class::Prototyped subclasses, or independent modules.
The advantage of calling install_mixins in the app_enter is that things
like the CGI object and the database connection are already set up, so I
can install classes that mess about with things like the database
session, like Class::DBI::Plugin::DateFormat::Oracle for example.
Hope this helps,
- Dotan.
Post by Andrew Gianni
I'd like to figure out how to get mixins up and running under
CGI::Prototype. I think I understand the technical aspects of
Class::Prototyped well enough to pull it off, but I have design questions
that I'd like feedback on. Assuming all goes well, I'd be happy to
contribute a CGI::Prototype::Mixin module to CPAN if I can get it that
generalized. Let me start with three scenarios I'd like to be able to
1. We have at least two applications that have pages that allow users to
upload files. The page is pretty similar in both applications with only a
few application specific things, like the table it's using and an extra
field. What I'd like to do is create an abstract page class for the file
upload page and then implement it with concrete page classes within the
applications themselves, overriding necessary methods to customize. I have
actually done this in the past by not giving the abstract class a base class
use base qw( Abstract::Page::Class My::App );
And that mostly works, but I'm knew that it could cause problems down the
line and after only a few months I found a scenario where it breaks: under
mod_perl, if I use the mixin page for more than one concrete page class in
my application and use Apache::Reload to reload the concrete page classes, I
get errors with whichever concrete page class is reloaded second. I don't
remember exactly what the error is, but I can look into it if anyone is
interested. Cracking plugability for this example is most important for me
because we actually have two more page types that we need to be able to do
this with and right now they're just duplicated in multiple projects.
2. I would like to create a pluggable diagnostics module that I could stick
in front of my app module to override core CGI::P methods at strategic
locations to capture performance data.
3. We currently have two different validation methodologies and I'm not sure
how soon we'll be picking one or the other to standardize on. I'd like to
create two separate mixin classes so I can choose the validation
functionality to use. I know I could make this work with something like a
Strategy pattern, defining two different validation classes with the same
interface and telling the app which one to use, but if I can get the first
two examples working, this could easily come along for the ride.
The basics of this from a technical perspective should be pretty straight
forward, right? I just need to take the mixin class and manipulate it's
parent and the parent of the class that I want to inherit from it, right?
The complicated part, and perhaps this doesn't have an easy answer, is what
it I want to insert more than one mixin? What if, for example,
CGI::Prototype::Hidden became a mixin, as Randal proposed some time ago (did
I understand that correctly?) and I also have an authentication mixin I'd
like to use. The problem is that, as a developer, I know the interfaces of
the two mixins and what they do, but I don't want to have to look at the
internals of each one to know if I need to put CGI::Prototype::Hidden
infront of CGI::Prototype::Mixin::Auth or vice versa. Is this actually a
connundrum or am I overanalyzing this? Any feedback or ideas would be
appreciated.
Andrew
--
Andrew Gianni - Lead Programmer Analyst
University at Buffalo, State University of New York
Computing and Information Technology / Administrative Computing Services
215 MFAC, Ellicott Complex, Buffalo, NY 14261-0026
716.645.5332 - AIM: andrewsgianni - http://www.newkenmore.com/agianni
Randal L. Schwartz
2007-08-16 17:32:31 UTC
Permalink
Andrew> Dotan, that looks like what I'm looking for and it's roughly what I
Andrew> had in mind although I didn't realize that a Class::Prototyped class
Andrew> could have multiple parents, which resolves the question of multiple
Andrew> mixins. How are they evaluated for execution? For example, if you
Andrew> override render_enter in Miner::Utils::Links and Miner::Utils::Text,
Andrew> will it just execute both of their render_enter methods in sequence in
Andrew> the order you added their parent slot in the foreach loop of
Andrew> install_mixins?

No, it picks "first found", which actually makes mixins a bit of pain. That's
why there's that particularly tricky code in that one project I did for you,
to essentially insert the "mixed-in" version in the middle of your inheritance
ladder, rather than hanging out on a second parent.

It's one of the weakest parts of the Class::Prototyped design, unfortunately,
and having chosen that for CGIP, I'm sorta stuck with it.
--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<***@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>
Perl/Unix/security consulting, Technical writing, Comedy, etc. etc.
See PerlTraining.Stonehenge.com for onsite and open-enrollment Perl training!
Andrew Gianni
2007-08-16 18:53:28 UTC
Permalink
Post by Randal L. Schwartz
Andrew> Dotan, that looks like what I'm looking for and it's roughly what I
Andrew> had in mind although I didn't realize that a Class::Prototyped class
Andrew> could have multiple parents, which resolves the question of multiple
Andrew> mixins. How are they evaluated for execution? For example, if you
Andrew> override render_enter in Miner::Utils::Links and Miner::Utils::Text,
Andrew> will it just execute both of their render_enter methods in sequence in
Andrew> the order you added their parent slot in the foreach loop of
Andrew> install_mixins?
No, it picks "first found", which actually makes mixins a bit of pain. That's
why there's that particularly tricky code in that one project I did for you,
to essentially insert the "mixed-in" version in the middle of your inheritance
ladder, rather than hanging out on a second parent.
Right, and that's actually what I'm looking for. In some cases, I need to
insert an abstract page class between my app class and a concrete page class
implementation. Sometimes, I need to insert an abstract class between my app
class and CGI::P::H. After staring at your code for a while it makes sense,
I just need to pick it up and move it somewhere more generally useful.

Dotan, are your mixins simply providing additional non-CGI::Prototype
functionality, i.e. not overriding core CGI::P methods?
Post by Randal L. Schwartz
It's one of the weakest parts of the Class::Prototyped design, unfortunately,
and having chosen that for CGIP, I'm sorta stuck with it.
So the upshot is that the developer needs to be aware of what the mixins are
doing and where there could be potential conflicts. As long as mixins call
reflect->super we should be in relatively good shape, right? But there may
be places where we don't always want to call super at all depending on the
functionality, which may cause unexpected results. Does this mean that
mixins probably won't be a good option for general CPAN modules, but only
for local classes that the developer understands the internals of? Or should
mixins only be restricted to core methods that *always* call super?

Andrew
--
Andrew Gianni - Lead Programmer Analyst
University at Buffalo, State University of New York
Computing and Information Technology / Administrative Computing Services
215 MFAC, Ellicott Complex, Buffalo, NY 14261-0026
716.645.5332 - AIM: andrewsgianni - http://www.newkenmore.com/agianni
Dotan Dimet
2007-08-19 17:01:05 UTC
Permalink
Post by Andrew Gianni
Dotan, are your mixins simply providing additional non-CGI::Prototype
functionality, i.e. not overriding core CGI::P methods?
Yep. That's why my mixins are "Utils" rather than pages. If you could
build an ordered chain of render_enter methods or whatever, the
activate() method would probably be much simpler. When I want to hook
some different functionality into a chain of execution, I have to
override a different hook; this isn't particularly scalable, but for my
simplistic app it works.

Loading...