The problem is that there are now legitimate reasons to "wrap" these
methods at the CHI/Driver.pm superclass level (meaning, do something
before and/or after the method). For example, I want to add an
optional generic size-awareness feature (the cache can keep track of
its own size), which means that we have to adjust size whenever
remove() and clear() are called. And I want to log remove() calls the
way we currently log get() and set().
So one solution is to define remove() and clear() in CHI/Driver.pm,
and have them call _remove() and _clear() in the driver subclasses.
But this kind of change makes me uneasy for several reasons:
* It changes the driver API, i.e. all existing drivers out there have
to modified. And we might have to change it again as we identify new
methods to wrap.
* The list of 'normal' versus 'underscore' methods becomes rather
arbitrary - it's "whatever we've needed to wrap so far".
Moose has before & after modifiers, but can they be defined in the
superclass and affect the subclass??
I guess CHI.pm could use a wrapping module, like Sub::Prepend or
Hook::LexWrap, on any driver class the first time it is used, e.g. the
first time someone says
CHI->new(driver => 'File')
we wrap the appropriate File methods. But this feels hacky - these
tools seem like a way to modify someone else's module, not as a
standard part of your own class.
Advice appreciated!
Thanks
Jon
Nope.
> I guess CHI.pm could use a wrapping module, like Sub::Prepend or
> Hook::LexWrap, on any driver class the first time it is used, e.g. the
> first time someone says
>
> CHI->new(driver => 'File')
>
> we wrap the appropriate File methods. But this feels hacky - these
> tools seem like a way to modify someone else's module, not as a
> standard part of your own class.
I don't believe that level of hackery is necessary.
The best way I can think of would be to throw your method modifiers
into a role that would be applied to the drivers. You then have the
choice of when and how you want to have that happen. You could
require drivers to apply it themselves or you could force its
application at runtime inside CHI.
-jdv
> CHI drivers implement methods like remove() and clear(). If you call
> $cache->remove(), it goes directly to the driver subclass.
>
> The problem is that there are now legitimate reasons to "wrap" these
> methods at the CHI/Driver.pm superclass level (meaning, do something
> before and/or after the method). For example, I want to add an
> optional generic size-awareness feature (the cache can keep track of
> its own size), which means that we have to adjust size whenever
> remove() and clear() are called. And I want to log remove() calls
> the way we currently log get() and set().
>
> So one solution is to define remove() and clear() in CHI/Driver.pm,
> and have them call _remove() and _clear() in the driver subclasses.
> But this kind of change makes me uneasy for several reasons:
> * It changes the driver API, i.e. all existing drivers out there
> have to modified. And we might have to change it again as we
> identify new methods to wrap.
> * The list of 'normal' versus 'underscore' methods becomes rather
> arbitrary - it's "whatever we've needed to wrap so far".
>
I thought about using regular wrapping modules, like Sub::Prepend or
Hook::LexWrap. But this fails when you have subclasses more than one
level deep. e.g.:
CHI::Driver -> CHI::Driver::Foo -> CHI::Driver::Foo::Bar
Now if you call CHI::Driver::Foo::Bar::remove(), the wrapping code
will get called twice, once for each subclass. I only want it to be
called once regardless of how deep the subclass is.
Here's how I solved this in CHI-0.2. When each CHI driver is used for
the first time, e.g. CHI::Driver::Memory:
my $cache = CHI->new('Memory');
CHI autogenerates a new class called
CHI::Wrapped::CHI::Driver::Memory, which inherits from
('CHI::Driver::Wrapper', 'CHI::Driver::Memory')
then blesses the actual cache object (and future cache objects of this
driver) as CHI::Wrapped::CHI::Driver::Memory.
Now, when someone calls a method like $cache->get() or $cache-
>remove(), CHI::Driver::Wrapper has an opportunity to handle it
first, and then pass control to CHI::Driver::Memory. If not, it goes
directly to CHI::Driver::Memory.
You can see the code here: http://cpansearch.perl.org/src/JSWARTZ/CHI-0.2/lib/CHI/Driver/Wrapper.pm
I was unable to find this solution on CPAN, even though I feel like I
must be reinventing the wheel. If someone knows of a distribution that
encapsulates this technique, please let me know.
Thanks
Jon
Seems like you're reinventing runtime composition of traits/roles.
http://search.cpan.org/~drolsky/Moose/lib/Moose/Cookbook/Roles/Recipe3.pod
http://search.cpan.org/perldoc?MooseX::Traits
http://blog.jrock.us/articles/MooseX%3A%3ATraits.pod
Tim.
Yup, that's what I was looking for - the ability to apply a role to an
instance, rather than a class. That prevents the double-application-of-
wrapping-code I was running into.
I have to leave my code in for now, because CHI is using Mouse, which
doesn't support applying a role to an instance. But at least I know I
can tear this out in the future.
Thanks!
Jon
Jon,
This is exactly what I meant back on 2/11/09 when I said
"application at runtime". I guess I could of been clearer
- sorry about that. This also shows how good the new Moose
docs are that Dave Rolsky put together. Nice job Dave!
-jdv