A Foolish Manifesto

fREWdiculous!

New stuff in DBIx::Class::Helpers 2.00200

A new release of the resplendent Perl ORM DBIx::Class means new release of DBIx::Class::Helpers

The ResultSet::Random helper had the wrong function used for MySQL. That was fixed thanks to an RT from pldoh.

get_namespace_parts from the util package was unnecessarily strict. Thanks to melo for the prodding to do that.

I refactored some of the code in core DBIx::Class so that I can more easily detect is_numeric with Row::NumifyGet, instead of requiring the user to specify it. Normally DBIx::Class autodetects it based on column type, but that code wasn’t quite generic enough until now. Nice!

And then the most exciting bit is a new helper entirely for the suite: Row::ToJSON. Basically I was sick of doing this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ACD::Schema::Result::Foo;

# regular package stuff here

sub TO_JSON {
  my $self = shift;
  return {
    id => $self->id,
    foo => $self->foo,
    # etc etc ad nausium
  }
}

"distraction";

Of course that can be shortened to:

1
2
3
4
sub TO_JSON {
  my $self = shift;
  return { map +( $_ => $self->$_), qw{id foo ...} }
}

But I still have to make that stupid columns list! This shiny new helper makes a TO_JSON method that will simply include all of your columns except for the “heavy” ones like TEXT, NTEXT, or BLOB. Of course you can have finer-grained control than that by explicitly saying to include (or not) a column in it’s configuration. See the docs for all the nitty gritty details.

  • 0 Comments
  • Filed under: Uncategorized
  • Do Passwords Right

    You all know not to put your passwords into the database in plaintext. Catalyst and DBIx::Class::EncodedColumn make doing this super easy and completely secure.

    First off, you might want to check out the wikipedia article about cryptographic hash functions. The gist of it though is this: a password stored in plain text is obviously compromised if the passwords file gets into the hands of evildoers. You can “hash” the passwords and they are now harder for the attackers to transform into plain-text. If your password is good it is nearly impossible, but basically what can happen is that the attacker uses the algorithm to generate hashes for every word in the dictionary or whatever and now they basically can crack all the basic passwords.

    You can take it a step further and “salt” your passwords (wikipedia salt article.) A simple way of doing that is just to concatenate some string onto the end of all of your passwords. This will make dictionary attempts useless unless they know your salt. Typically when using a salt the salt is kept secret.

    And then you can have a unique salt per password. Imagine a scheme where the salt is $username$id. It would require the attackers to basically generate a dictionary per user!

    The scheme we’ve settled on uses Eksblowfish. The DBIC Component for it actually uses a 16 character randomly generated salt for every password. Nice!

    Ok, so how does one apply such sweet, sweet code? First, (always) set up your model. This is a slightly trimmed version of ours:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package MTSI::Schema::Result::User;

    use strict;
    use warnings;

    use parent 'DBIx::Class::Core';

    use CLASS;

    CLASS->load_components(qw/EncodedColumn/);

    CLASS->table('users');

    CLASS->add_columns(
       id => {
          data_type         => 'integer',
          is_numeric        => 1,
          is_nullable       => 0,
          is_auto_increment => 1,
       },
       username => {
          data_type         => 'varchar',
          size              => 50,
          is_nullable       => 0,
       },
       password => {
          data_type     => 'CHAR',
          size          => 59,
          encode_column => 1,
          encode_class  => 'Crypt::Eksblowfish::Bcrypt',
          encode_args   => { key_nul => 0, cost => 8 },
          encode_check_method => 'check_password',
    });

    CLASS->set_primary_key('id');

    1;

    Easy peasy! The encode_check_method option for password basically puts a method in your result class that you can call with a plaintext password and it returns true or false if the password is legitimate. The nice thing about that is that if you decide to switch to some other kind of hashing, your controller stays the same. Model code for the win!

    Next up, the Catalyst configuration. This was what took me a while to find, but thanks to mst I finally found it yesterday. The package we use for auth is the same one everyone uses in Cat: Catalyst::Plugin::Authentication. The docs that I was looking for specifically were the ones for Catalyst::Authentication::Credential::Password. So after reading those docs, the following is the catalyst config snippet one would use for these nice passwords:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       'Plugin::Authentication' => {
          use_session => 1,
          default => {
             credential => {
                class              => 'Password',
                password_type      => 'self_check',
             },
             store => {
                class                     => 'DBIx::Class',
                user_model                => 'DB::User', #<-- DB refers to the name of the
                role_relation             => 'roles',         #     model class we are using
                use_userdata_from_session => 1,
             }
          }
       },

    Note: the password_type of self_check is what tells the controller to just call $result->check_password($plaintext).

    So there you have it. That’s all the code you need for secure passwords with Catalyst. If you make a new project and your users passwords get compromised it is your fault.

    Have a nice day :-)

  • 3 Comments
  • Filed under: Uncategorized
  • An Exposition on Specific Time Saving Code

    I write a lot of ExtJS grids at work. I have written JavaScript classes for our Ext grids that generate as much as possible automatically, but the actual column definitions of the grids are almost always unique. The project I am on now is nearing our first real deploy, and we’re late, so things have been really, really busy.

    It wasn’t until recently that I realized just how much time I spent working on grids and their related records (representation of the rows of a grid.) Although even if I’d known just how much I do this at the beginning of the project, I certainly didn’t know DBIx::Class as much as I do now, in addition to the other 4 non-core modules that I’ll mention.

    Because I’ve been working all these super long hours (12-14 a day), often after a frustrating day I’ll try to code something more relaxing and rewarding at home or at work but not during work. So today I decided to finally take the plunge and do what I’ve been pondering for a few months and write some code to generate Ext scaffolding for me. So I’m going to walk you through the script that I wrote (and will probably work on more as time goes by.)

    This isn’t a full module, not even in my repo yet, so it’s all just in one file. Here’s the boilerplate intro:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!perl

    use strict;
    use warnings;

    use feature ':5.10';

    use Syntax::Keyword::Gather;
    use String::CamelCase ();
    use Lingua::EN::Inflect ();
    use List::Util;
    use Statistics::Basic 'mean';

    That’s pretty basic. We use a bunch of modules that I’ve used in at least 1 other project before and was happy with.

    1
    2
    3
    4
    5
    6
    7
    8
    use FindBin;
    use lib "$FindBin::Bin/../local/lib", "$FindBin::Bin/../lib";
    use ACD::Schema;
    use Config::JFDI;
    my $config = Config::JFDI->new(name => 'acd', path => "$FindBin::Bin/../acd");
    my $config_hash = $config->get;
    my $connect_info = $config_hash->{Model}{DB}{connect_info};
    my $schema = ACD::Schema->connect($connect_info);

    That’s the code to parse any kind of Catalyst config file and the grab a new schema based on it. It’s a ton of biolerplate but I live with it.

    So next up is a basic inflection function that gives us all the different forms of a word we might need. It starts with either “single_foo” or “SingleBar” and gives us six variations based on that:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    sub inflect {
       my $word = shift;

       my $return = { singular => {}, plural => {}};
       if ( defined $word->{camel}) {
          $word = $word->{camel};
          $return->{singular}{camel} = $word;
          $return->{singular}{noncamel} = String::CamelCase::decamelize($word);
       } else {
          $word = $word->{noncamel};
          $return->{singular}{camel} = String::CamelCase::camelize($word),
          $return->{singular}{noncamel} = $word;
       }
       $return->{singular}{human} = join(
          q{ }, map ucfirst $_,
          split /_/, $return->{singular}{noncamel}
       );

       $return->{plural}{noncamel} = join( q{_}, split / /,
          Lingua::EN::Inflect::PL(
             join q{ },
             split /_/,
             $return->{singular}{noncamel}
          )
       );
       $return->{plural}{camel} = String::CamelCase::camelize(
          $return->{plural}{noncamel}
       );
       $return->{plural}{human} = join( q{ }, map ucfirst $_, split /_/,
          $return->{plural}{noncamel}
       );
       return $return
    }

    So that uses String::CamelCase and Lingua::EN::Inflect combined with join and split mostly.

    We’re *almost* ready to generate a record. But first we need to define a mapping from the data type in the database to the data type that Ext uses:

    1
    2
    3
    4
    5
    6
    7
    8
    my $types_xform = {
       int => 'int',
       float => 'float',
       varchar => 'string',
       bit => 'boolean',
       datetime => 'date',
       money => 'float',
    };

    Ok, now let’s look at the code to generate an Ext.record:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    sub generate_record {
       my $schema = shift;
       my $source_name = shift;

       my $source = $schema->source($source_name);

       return qq{Ext.ns('ACDRI.record');

    ACDRI.record.$source_name = Ext.data.Record.create([\n} .
       join(qq{,\n}, sort { $a cmp $b } gather {
          for (map [$_, $source->column_info($_)], $source->columns) {
             my ($column, $info) = @{$_};
             take "   {name: '$column', type: '$types_xform->{$info->{data_type}}'}";
          }
       }) .  "\n]);";

    }

    So what’s going on here is that we get the source from the schema. The source could be considered something like a table definition, although it can also point at a view or whatever too. Then we start generating the string representing our record, and then we use a join/gather combo to get the column data the way we want it.

    We could certainly just use a more complex map instead of the for+gather that we have, but I personally feel than any map where you *must* use the block form is cumbersome. So we join together all of the strings that gather took, and then append the end of the definition, and voilà, we have a record!

    Here’s example output on our Customer source:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Ext.ns('ACDRI.record');

    ACDRI.record.Customer = Ext.data.Record.create([
       {name: 'color_code', type: 'string'},
       {name: 'comments', type: 'string'},
       {name: 'id', type: 'string'},
       {name: 'last_edited_date', type: 'date'},
       {name: 'name', type: 'string'},
       {name: 'price_approval_required', type: 'boolean'},
       {name: 'sales_representative_id', type: 'int'}
    ]);

    If you’re still following along you should be fine with the next, much more impressive functionality.

    I’d like to have the script do as much as possible to speed up my work, so let’s see how far we can take this. First, I made a mapping from type to renderer, so that things that are datetimes use our custom datetime renderer, same with booleans:

    1
    2
    3
    4
    my $renderer_from_type = {
       datetime => 'ACDRI.fn.Renderers.dateTime',
       bit => 'ACDRI.fn.Renderers.bool',
    };

    Next, I wrote a function that would try it’s best to guess how wide a column should be based on it’s header and the average width of the strings inside of it:

    1
    2
    3
    4
    5
    6
    7
    sub avg_width {
       my ($rs, $col, $header) = @_;
       List::Util::max (
          mean(map +( defined($_) ? length "$_" : 0 ), $rs->get_column($col)->all), # average width of field
          length $header                                                            # col header
       );
    }

    Now finally we have the grid function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    sub generate_grid {
       my $schema = shift;
       my $source_name = shift;

       my $source_names = inflect({ camel => $source_name });
       my $source = $schema->source($source_name);

       return qq`/*global Ext */
    /*global ACDRI */
    /*global MTSI */
    Ext.ns('ACDRI.ui.grid');

    /**
     * \@class ACDRI.ui.grid.$source_names->{plural}{camel}
     * \@extends MTSI.ui.Grid
     */
    ACDRI.ui.grid.$source_names->{plural}{camel} = Ext.extend(MTSI.ui.Grid, {
       title: '$source_names->{plural}{human}',
       record: ACDRI.record.$source_names->{singular}{camel},
       updateConfig: {
          xtype: 'ACDRI.ui.form.$source_names->{singular}{camel}'
       },
       addConfig: {
          xtype: 'ACDRI.ui.form.$source_names->{singular}{camel}',
       },
       initComponent: function () {
          //this.sortInfo = {
          //   field: 'part_id',
          //   direction: 'asc'
          //};
          var config = {
             url: '$source_names->{plural}{noncamel}',
             itemName: '$source_names->{singular}{human}',
             columns: [`
    .
       join (qq{, }, gather {
          for (map [$_, $source->column_info($_)], $source->columns) {
             my ($column, $info) = @{$_};
             my $colnames = inflect({ noncamel => $column });
             my $renderer = $renderer_from_type->{$info->{data_type}}
                ? "\n            renderer: $renderer_from_type->{$info->{data_type}},"
                : '';
             my $width;
             given ($info->{data_type}) {
                when ('varchar') {
                   $width = int 7*avg_width(
                      $source->resultset, $column, $colnames->{singular}{human}
                   );
                }
                when ('datetime') {
                   $width = List::Util::max(
                      int 7 * length $colnames->{singular}{human},
                      57
                   );
                }
                default {
                   $width = int 7 * length $colnames->{singular}{human};
                }
             }
             take qq[{
                header: '$colnames->{singular}{human}',
                dataIndex: '$column',
                sortable: true,
                hidden: false,$renderer
                width: $width
             }];
          }
       }) .  qq`]
          };

          Ext.apply(this, Ext.apply(this.initialConfig, config));
          ACDRI.ui.grid.$source_names->{plural}{camel}.superclass.initComponent.apply(this, arguments);
       }
    });

    Ext.reg('ACDRI.ui.grid.$source_names->{plural}{camel}', ACDRI.ui.grid.$source_names->{plural}{camel});
    `
    ;
    }

    We see nearly the same structure here that we did with the record, except much more intricate. Notice the code to generate the width uses the magic constant 7. As I played with this I found that 7 seemed to work for the font that Ext uses by default (Arial?) Optimally I would actually use some kind of metrics package to ask it for the width of all of the strings that I generated in the function above and average that, instead of averaging character lengths. But this seems to work really, really well, so the ROI is pretty good.

    Also note the code to pick and insert the renderer. It’s not complex or anything, but it yields very convenient results. Here’s the output of running this one on the same source we used for the record:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    /*global Ext */
    /*global ACDRI */
    /*global MTSI */
    Ext.ns('ACDRI.ui.grid');

    /**
     * @class ACDRI.ui.grid.Customers
     * @extends MTSI.ui.Grid
     */

    ACDRI.ui.grid.Customers = Ext.extend(MTSI.ui.Grid, {
       title: 'Customers',
       record: ACDRI.record.Customer,
       updateConfig: {
          xtype: 'ACDRI.ui.form.Customer'
       },
       addConfig: {
          xtype: 'ACDRI.ui.form.Customer',
       },
       initComponent: function () {
          //this.sortInfo = {
          //   field: 'part_id',
          //   direction: 'asc'
          //};
          var config = {
             url: 'customers',
             itemName: 'Customer',
             columns: [{
                header: 'Comments',
                dataIndex: 'comments',
                sortable: true,
                hidden: false,
                width: 226
             }, {
                header: 'Color Code',
                dataIndex: 'color_code',
                sortable: true,
                hidden: false,
                width: 70
             }, {
                header: 'Id',
                dataIndex: 'id',
                sortable: true,
                hidden: false,
                width: 35
             }, {
                header: 'Last Edited Date',
                dataIndex: 'last_edited_date',
                sortable: true,
                hidden: false,
                renderer: ACDRI.fn.Renderers.dateTime,
                width: 112
             }, {
                header: 'Name',
                dataIndex: 'name',
                sortable: true,
                hidden: false,
                width: 136
             }, {
                header: 'Price Approval Required',
                dataIndex: 'price_approval_required',
                sortable: true,
                hidden: false,
                renderer: ACDRI.fn.Renderers.bool,
                width: 161
             }, {
                header: 'Sales Representative Id',
                dataIndex: 'sales_representative_id',
                sortable: true,
                hidden: false,
                width: 161
             }]
          };

          Ext.apply(this, Ext.apply(this.initialConfig, config));
          ACDRI.ui.grid.Customers.superclass.initComponent.apply(this, arguments);
       }
    });

    Ext.reg('ACDRI.ui.grid.Customers', ACDRI.ui.grid.Customers);

    Note that there are probably some things that we could do a little better. For instance, we should probably check for text fields and not include them in the grid. We could also be clever and look for anything ending with _id and have it hidden by default. But in general, the above is very nice results. At the very least you are probably going to need to reorder the columns (although you could give some kind of grid_order_id to the column definition in the Schema::Result code, but I don’t think that’s worth it.)

    So I hope someone else enjoyed reading this as much as I did writing it. After I’ve exhausted all of the little Ext things I can use this for (form, etc) I will probably look into messing with DBICx::AutoDoc to add in Dia outputs.

  • 2 Comments
  • Filed under: Uncategorized
  • Template.Tiny

    (Sorry if you heard this already :-) )

    At $work we do as much “view” type code as we can in JavaScript with the ExtJS framework. I have personally found it to be a great framework to work with, although often it is lacking in the non-UI department. One thing that at first I really liked about Ext was their Template and XTemplate classes. But as time went on I got more and more annoyed with those modules.

    I’ve always thought that Template-Toolkit was a really nice templating library to work with. I hate templating html because of all the weird little gotchas having to do with CSS and whatnot, but doing almost all that kind of work in JavaScript I have started missing Template-Toolkit. At some point I heard about Template::Tiny and this past Saturday I thought, “that’s like, 160 LOC…I could probably port that to javascript!”

    So I did! It’s not really done yet, but the current code is at github. I need to finish porting the test suite from Perl 5 to JavaScript so that I can ensure correctness (I am certain I screwed up some stuff.)

    Alias mentioned in his post that I use XRegExp (600 LOC) to help out with the Regular Expression support in JavaScript for this module. I actually wasn’t going to, since the only thing I needed it for was the /x flag, or to be more clear telling the parser to ignore whitespace, but I want to keep Template.Tiny in sync with the Perl version, and I really don’t want to strip out all the whitespace by hand. If someone takes issue with the dep they can fork away :-)

    So once it is fully ported I fully intend to use it entirely from now on instead of the Ext templating. But take note, Ext templates and Template.Tinies (what?) really solve different problems and have different goals. The following is a list of Pros and Cons of each:

    Ext.(X)Template

    Pros:

    • Can be compiled to JavaScript, for speed
    • Allows complex expressions in “if” blocks (see Example 1)
    • Interesting “topicalizing” feature (Example 2)
    • Crashes when you leave out a variable (no mystery as to why a field is blank) (Example 3)
    • Can execute arbitrary JavaScript code in a template for complex stuff (Example 4)
    • Neat automatic “current item” style variables when you are iterating over an array (Example 5)
    • Basic Math Support
    • Ability to call functions associated with Template object
    • Very cool builtin Renderer support (Example 6)

    Cons:

    • No else if. If you want something like that you must do if !expr. Lame.
    • Crashes when you leave out a variable (mostly that’s just annoying)
    • This is almost entirely subjective, but having xml as a templating thing is kinda gross.
    • Not really open source, so I can’t use it in personal projects and have people use my code in a corporate setting

    Example 1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var data = { age: 21 };

    // XTPL
    var tpl = new Ext.XTemplate( "<tpl if='age > 18'>Can Vote!</tpl>");
    var str = tpl.apply(data);

    // TT
    var tmp = new Template.Tiny();
    var str = tmp.process(
       "[% IF old_enough %]Can Vote![% END %]",
       { old_enough: (age > 18 )}
    );

    Example 2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var data = { person: {first_name: 'fREW', last_name: 'Schmidt', title: 'Mr.' }};
    // XTPL
    var id = new Ext.XTemplate( "<tpl for='person'>{title} {first_name} {last_name}</tpl>");
    var str = tpl.apply(data);

    // TT
    var tmp = new Template.Tiny();
    var str = tmp.process(
       "[% person.title %] [% person.first_name %] [% person.last_name %]",
       data
    );

    Example 3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var data = { werld: 'fail'};

    // XTPL
    var tpl = new Ext.XTemplate( "Hello {world}");
    var str = tpl.apply(data); // error message

    // TT
    var tmp = new Template.Tiny();
    var str = tmp.process(
       "Hello [% world %]",
       data
    ); // silent failure

    Example 4:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var data = { foo: 1 };

    // XTPL
    var id = new Ext.XTemplate( "{[someComplexRenderer(values.foo)]");
    var str = tpl.apply({ foo: 1});

    // TT
    var tmp = new Template.Tiny();
    var str = tmp.process(
       "[% foo %]",
       { foo: someComplexRenderer(data.foo) }
    );

    Example 5:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var data = { arr: ['foo','bar','baz']};

    // XTPL
    var tpl = new Ext.XTemplate('<tpl for="arr">({#}. {.})</tpl>');
    var str = tpl.apply(data); // "(1. foo)(2. bar)(3. baz)";

    // TT
    var tmp = new Template.Tiny();
    var idx = 0;
    var str = tmp.process(
       "[% FOREACH x IN arr %]([% x.i %]. [% x.var %][% END %]",
       { arr: data.arr.map(function(x) { return { i: idx++, var: x } } }
    );

    Example 6:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var data = { longtext: "this won't fit!"};

    // XTPL
    var tpl = new Ext.XTemplate('{longtext:ellipsis(5)}');
    var str = tpl.apply(data); // "this ...";

    // TT
    var tmp = new Template.Tiny();
    var str = tmp.process(
       "[% longtext %]",
       { longtext: Ext.util.Format( data.longtext, 5) }
    );

    Template.Tiny

    Pros:

    • Has IF/ELSE (Example 7)
    • Doesn’t crash on undefined fields
    • Nicely Licenced (Perl License)

    Cons:

    • No complex expressions, math, or external javascript support
    • No topicalizing
    • Doesn’t crash on undefined fields (could be nice for debugging)
    • Probably slower (haven’t checked that yet)

    Example 7:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
       var data = { jack_slocum: 1 };

       // TT
       var tmp = new Template.Tiny();
       var str = tmp.process(
          "[% IF jack_slocum %]We don't appreciate else-ifs[% ELSE %] Woot![% END %]",
          data
       );

       // XTPL
       var tpl = new Ext.XTemplate(
          '<tpl if="jack_slocum">We don\'t appreciate else-ifs</tpl><tpl if="!jack_slocum">Woot!</tpl>'
          );
       var str = tpl.apply(data);

    I am planning on making a wrapper for TT for our stuff that will allow an anonymous function that will do data transformation like above. But as you can see above XTemplate really has more to offer, it just annoys the heck out of me on a regular basis :-)

  • 1 Comment
  • Filed under: Uncategorized
  • Solution on how to serialize dates nicely

    So after discussing this problem with the inimitable ribasushi we came up with a good solution. It’s not quite generic, but it solves the current problem very nicely. First, we subclass DateTime:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package MTSI::DateTime;
    use strict;
    use warnings;

    use parent 'DateTime';

    sub TO_JSON { shift->ymd }

    1;

    Next, in the base class we use for all of our Result classes in our Schema, we override _inflate_to_datetime to rebless the returned value into our subclass:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package ACD::Schema::Result;
    use strict;
    use warnings;

    use parent 'DBIx::Class::Core';

    __PACKAGE__->load_components(qw{
       TimeStamp
       Helper::Row::NumifyGet
    });

    use MTSI::DateTime;

    sub _inflate_to_datetime {
       my $self = shift;
       my $val = $self->next::method(@_);

       return bless $val, 'MTSI::DateTime';
    }

    1;

    And lastly, in our JSON view, we ensure that convert_blessed is on so that json will automatically call our TO_JSON method:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package ACD::View::JSON;

    use Moose;
    extends 'Catalyst::View::JSON';

    use JSON::XS ();

    has encoder => (
       is => 'ro',
       lazy_build => 1,
    );

    sub _build_encoder {
       my $self = shift;
       return JSON::XS->new->utf8->convert_blessed;
    }


    sub encode_json {
       my($self, $c, $data) = @_;
       $self->encoder->encode($data);
    }

    1;

    And that’s all there is to it! Thanks Perl for allowing me to rebless my objects :-)

  • 0 Comments
  • Filed under: Uncategorized
  • Background: dates in our database automatically get “inflated” to DateTime objects. That works pretty much perfectly. We use JSON to serialize all of our objects to go to our JavaScript stuff on the client side. The way that works is basically like the following:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # this should probably be called something more generic, like serialize
    # but this decision was made by someone else and I'm not going to
    # spend time solving that for now
    sub TO_JSON {
       my $self = shift;
       return {
          map +( $_ => $self->$_),
             qw{id name when_created}
       };
    }

    which expands to:

    1
    2
    3
    4
    5
    {
       id => 1,
       name => 'frew',
       when_created => DateTime->new(...),  # <-- not ok
    }

    Problematically, DateTime has no TO_JSON method. I see two solutions to this, both of which kinda suck.

    Monkey Patch

    I could do something like:

    1
    2
    3
    no strict 'refs';
    *DateTime::TO_JSON = sub { shift->ymd };
    use strict;

    but we all know that Monkey Patching is Sketch Towne City. Since perl is a prototypical language there is certainly a way to do something like:

    1
    2
    my $f = DateTime->now;
    $f::TO_JSON = sub { shift->ymd };

    but I couldn’t really figure out how to do it. And even if I could that (probably easy with Class::MOP) I’d still have to make hooks to do that to all to all of our DateTime objects (which still sucks.) And then I could effectively do the same thing…

    Subclass DateTime

    1
    2
    3
    4
    package DateTime::Frew;
    use parent 'DateTime';

    sub TO_JSON { shift->ymd };

    And then do some kind of trickery in DBIC-land to make the DateTime instantiation able to use other classes. But that’s not as simple as it might sound (due to design issues in DBIC that are not easy to solve as far as I can see.)

    I’m pretty sure that there is a good solution that I’m missing. What is it? Can anyone tell me?

  • 3 Comments
  • Filed under: Uncategorized
  • So Long IronMan….Sortof…

    The IronMan initiative is awesome! No one should misunderstand that. But I am getting WAY TOO MANY things in my feed reader. So here’s my solution:

    1. unsubscribe from from the vanilla feed.
    2. Add the OPML to my feed reader and then unsubscribe from the non-english and spam feeds
    3. work on the IronMan code base to add another feed of when people add their feed, and then subscribe to that so I know when people join
    4. profit!

    Anyway, if anyone wants to help with that second to last one I’d appreciate it since plagger is CRAZY.

  • 1 Comment
  • Filed under: Uncategorized
  • Latest additions to DBIC::Helpers

    Yesterday I added a basic but really nice helper to DBIx::Class::Helpers. Say hello to DBIx::Class::Helper::Row::NumifyGet. The reasoning is that often we have bit fields in our database and when we serialize them with JSON we get something like the following:

    1
    { 'bit_field':'0'}

    JavaScript has the whole truthy concept like Perl except that in JavaScript “0″ is true, while 0 is false. So NumifyGet will automatically “numify” columns with the is_numeric field set to true. After doing that the json above would become:

    1
    { 'bit_field':0}

    Much nicer. Also I added some good docs to DBIx::Class::Helper::ResultSet::Union as well as fixing some latent bugs that were in it.

    Enjoy!

  • 1 Comment
  • Filed under: Uncategorized
  • Getting portable

    One of my Goals for the New Year was to finish my current project at work. One thing that keeps me from working more on my project is that working from home is pretty painful. So I decided that I’d do all that I could to do as much work as possible from home without needing to be VPN’d in to work.

    The primary hurdle was to figure out a way to get all of the data from our shared dev server (SQL Server 2005) to something I could use at home. Once I put my mind to it it wasn’t very hard at all! With a little nudge from ribasushi I got the following code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    my $schema = ACD::Schema->connect($connect_info);

    my $sqema = ACD::Schema->connect('dbi:SQLite:dbname=database');

    $sqema->deploy();
    for ( $schema->sources ) {
        my $old_rs = $schema->resultset($_);
        my $new_rs = $sqema->resultset($_);
        $old_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
        $new_rs->populate([$old_rs->all]);
    }

    Of course I realized after that that I had left most of our DBIC column def’s blank and I had to fill in is_nullable all over the place, but that was just a bunch of regular work.

    So after that I had a nice, 200 meg database file that I could use on my laptop just fine.

    The next thing I did was set up SVK on my laptop. git is the new hotness but without a gui and good windows support there’s no way I’m going to be able to get my coworkers to use it; svk gives a lot of the benefit of git to svn users. Here’s how I did it (all from here.)

    1
    2
    3
    4
    svk mirror //acd/trunk svn://svn.lan.mitsi.com/aircraft_ducting
    svk sync //acd/trunk
    svk cp -m 'local branch for acd' //acd/trunk //acd/local
    svk co //acd/local acd

    That worked without a hitch. The next thing was to install what we need to run our app on my laptop. That’s not so bad since I keep our Makefile.PL up to date:

    1
    2
    perl Makefile.PL
    make installdeps

    Last but not least, I moved my sqlite database into my application and updated our acd.json file to look like the following:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
       "name": "ACD",
       "Model": {
          "DB": {
             "connect_info":{
                "dsn":"dbi:SQLite:dbname=database",
                "quote_char":"\"",
                "name_sep":".",
             }
          }
       }
    }

    And it works! Now I can work from home extremely easily!

  • 0 Comments
  • Filed under: Uncategorized
  • Goals Update

    This week I…

    1. paid of 25% of my college loans (!!!)
    2. scheduled a pickup from goodwill for clothes to get rid of next week
    3. went in to work on Saturday to get caught up on some stuff or our project
    4. and planned a dinner for myself and friends for next week that I’ll be cooking

    A good start if I may say so myself.

  • 1 Comment
  • Filed under: Life