June 2014 Archives

Moose MonkeyPatching With a Mock UserAgent

In this post: https://blogs.perl.org/users/samuel_kaufman/2014/06/when-a-fat-comma-is-confusing.html I mentioned I was using Test::LWP::UserAgent for my test. One thing I struggled with was testing ( with a mockup ) a service which was being instantiated completely beyond the scope of the test- so even though it took ua as a parameter to the constructor to override the default LWP::UserAgent->new, I couldn't reach down inside the Catalyst controller where it instantiated a module with the default parameters from the test.

So in case anyone else has had similar issues, here's how I got it working:

use Test::LWP::UserAgent;
use HTTP::Message::PSGI;
use Moose::Util qw[ find_meta ];

my $ua = Test::LWP::UserAgent->new(cookie_jar => {} );
my $meta = find_meta('SocialFlow::LinkShortening');
$meta->make_mutable;
$meta->add_around_method_modifier( ua => sub { $ua } );
$meta->make_immutable;
$ua->register_psgi( 'socialflow',$script->psgi_app);

It's not pretty, and I can think of plenty of reasons why this is a bad idea, but it works! Looking at it now I could've probably made 'ua' lazy_build and put an around modifier to hijack _build_ua but that's also breaking encapsulation....

When a fat comma is confusing

I frequently swap out commas for fat commas ( => ) when I think it reads better. One place this bites me in the ass is when I reread code where I'm using a function that takes two parameters but look like they could take more. This recently happened to me with Test::LWP::UserAgent.

I started with the docs' example:

$test_ua->map_response(
  "myapp",
  sub { HTTP::Response->from_psgi($app->($_[0]->to_psgi)) },
);

Then thought this would look prettier:

$test_ua->map_response(
  myapp =>
  sub { HTTP::Response->from_psgi($app->($_[0]->to_psgi)) },
);

Then of course I came back two days later and added:

$test_ua->map_response(
  myapp =>
  sub { HTTP::Response->from_psgi($app->($_[0]->to_psgi)) },
  myotherapp => 
    sub { HTTP::Response->from_psgi($app->($_[0]->to_psgi)) },
);

It took me at least an hour to figure that one out. I haven't extracted a real life lesson out of this yet.. I'd be tempted to never use fat commas for a function that expects positional parameters... but I'm not going to start rewriting all my Moo(se) classes to

has 'bah', ( is => 'ro', );

Pagination done the PostgreSQL way done the DBIx::Class way

Recently, I read Pagination Done the Postgresql Way. The premise is that offset/limit combined with a date index gets slower as you page, but if you page in a way where you are always selecting by date then you will be using the index properly.

A simple example would be a table with an entry per day and a date index. Asking for Tuesday would use the date index more effectively than asking for the 3rd entry of the table sorted ascending, so you could page by just asking for an entry with a date greater than the previous one you pulled limit 1.

Example in perl using DBIx::Class: ( gist )

my $dtf = $schema->storage->datetime_parser;
sub _rs {
    my ( $prev_date, $prev_id ) = @_;
    my $search = undef;
    my $attr   = {
        prefetch => 'content_item',
        order_by => { -desc => [ 'me.content_item_id', 'me.published_date' ] },
        rows     => 5000,
    };
    if ($prev_date) {
        $search = \[
            '(me.content_item_id,me.published_date) < (?,?)',
            $prev_id, $dtf->format_datetime($prev_date),
        ];
    }
    return $schema->resultset("ContentItemPublishedDate")
        ->search( $search, $attr );
}

my $rs = _rs;
while ( my @rows = $rs->all ) {
    for (@rows) {
        my $content = $utf8->decode( $_->content_item->content );
        #WORK HAPPENS HERE
    }
    my $last_row = $rows[$#rows];
    $rs = _rs($last_row->published_date,$last_row->content_item_id);
}

About Samuel Kaufman

user-pic CTO / Codemonkey, Socialflow.com