So, I have this website, and I've finished revamping the design. The new design not only looks spiffier, but it's put together on the back-end via a template and Template::Toolkit.
Feel free to check it out and let me know what you think!
So, I have this website, and I've finished revamping the design. The new design not only looks spiffier, but it's put together on the back-end via a template and Template::Toolkit.
Feel free to check it out and let me know what you think!
The latest versions of Hypatia and Hypatia::Chart::Clicker (versions 0.025 and 0.02, respectively) have hit CPAN (and, of course, they're also up on GitHub).
The main feature is the added robustness of not necessarily requiring a columns attribute (specifying, for example, which column names correspond to x-values and which correspond to y-values), and trying to Do The Right Thing if you leave it out.
So, to twist the example in my previous Hypatia post, we can do the following:
use strict;
use warnings;
use Hypatia;
my $hypatia=Hypatia->new({
back_end=>"Chart::Clicker",
graph_type=>"Line",
dbi=>{
dsn=>"dbi:MySQL:dbname=database;host=localhost",
username=>"jdoe",
password=>"sooperseekrit",
query=>"select datediff(now(),date_created) as num_days_ago,sum(revenue) as daily_revenue
from widget_sales
group by datediff(now(),date_created) as num_days_ago"
}
});
#grabs data from the query and puts it into a Chart::Clicker line graph
my $cc=$hypatia->chart;
#Since $cc is a Chart::Clicker object, we can now do whatever we want to it.
$cc->title->text("Total Daily Revenue for Widget Sales");
$cc->write_output("daily_revenue.png");
and Hypatia will be able to detect that you (presumably) meant for the num_days_ago to yield x-values and daily_revenue to yield y-values.
I also put together a large battery of unit tests (probably too large...) to help ensure that these guesses work correctly. Take a look and tell me what you think.
PS My thanks to wk who noticed the bug in my previous (fictitious) example: namely that all of the graphs via Hypatia by way of Chart::Clicker require numeric x-values (and not dates).
So, to troubleshoot a potential bug found by wk (as mentioned in the comment thread of this post), I realized I had to set up MySQL (since I don't have an installation of it to play with at work).
Thankfully, setting up a MySQL instance on my web server was made fairly painless due to Plesk. So, all I have to do now is install DBD::mysql, and I should be good to go, right? Well, that's what I thought... Unfortunately, the make step failed to work, due to what seemed to be a bunch of cascading linker errors, and it seemed to start with mysql.h not being found.
Some rounds of Googling brought me to this MySQL bug report page, where it was suggested that mysql-devel needed to be installed.
Okay...so, one yum install mysql-devel later, and I'm met with the following:
Running rpm_check_debug
ERROR with rpm_check_debug vs depsolve:
mysql is needed by (installed) mysql-server-5.5.14-11071510.x86_64
mysql is needed by (installed) plesk-mysql-5.5-11053114.x86_64
Complete!
*sigh* So, some more Googling brought me to an answer left by a kind soul that contains a link to a specific RPM. Miraculously, the RPM installed and I was then able to successfully install DBD::mysql.
I swear, running my own Linux box (well, okay, a VM, but still...) sometimes feels like I'm playing an old-school adventure game.
It took longer than I would've liked--partially due to $work and partially due to other issues--but Hypatia has finally been released!
As explained in a previous post, Hypatia is an API layer that sits between DBI and data visualization modules. For the moment, only Chart::Clicker is supported as a visualization back-end, but work is underway on bindings for GraphViz2.*
There's aexamples folder with the distribution of Hypatia::Chart::Clicker, but here's a possible (fictitious) example:
use strict;
use warnings;
use Hypatia;
my $hypatia=Hypatia->new({
back_end=>"Chart::Clicker",
graph_type=>"Line",
dbi=>{
dsn=>"dbi:MySQL:dbname=database;host=localhost",
username=>"jdoe",
password=>"sooperseekrit",
query=>"select DATE(time_of_sale) as date,sum(revenue) as daily_revenue
from widget_sales
group by DATE(time_of_sale)"
},
columns=>{"x"=>"date","y"=>"daily_revenue"}
});
#grabs data from the query and puts it into a Chart::Clicker line graph
my $cc=$hypatia->chart;
#Since $cc is a Chart::Clicker object, we can now do whatever we want to it.
$cc->title->text("Total Daily Revenue for Widget Sales");
$cc->write_output("daily_revenue.png");
Oh, and in case you're wondering how the module got its name, Hypatia was one of the librarians of the Great Library of Alexandria and one of the first female names in mathematics.
Is it the best code that anyone's ever written? No. Does it have bugs? Quite possibly, although I did release it with some unit tests and tried to eliminate every bug I could find. Of course, bug reports and constructive criticism are more than welcome.
When using BUILDARGS, make sure to return the class's constructor when you're done. That would've saved me some of the hairs that I've been ripping out of my head...
Also, MooseX::AbstractFactory makes dependency injection a bit easier, although its route handling is a bit limited in that it only takes one string argument to determine an implementation. I've worked around this for now, but in the long run, I think I'll make a wrapper class (or maybe even a patch?). I'm envisioning something that provides Dancer-esque "route handlers" to objects that would add more customizability--and perhaps even the ability to generate new classes on the fly (maybe...that would probably involve prodding into the Dark Magic behind Moose, which is a daunting task).
loading the module given by the back_end attribute and moving merrily upon our way is that these modules have lots of methods (eg chart) that should be used by the base object. However, this is not insurmountable, and Moose provides two ways to get around this (TIMTOWTDI, after all): loading dependencies as roles or as modules.Etc, etc. As of right now, class and race are just strings. However, there may be a lot of things that depend on race and class, such as stats (eg halflings are more dexterous than dwarves but are weaker), skills/abilities (elves are better with magic but are more frail), and both classes and races can have equipment restrictions (halflings can't wield polearms and mages can't wear plate mail). Now yes, it's possible to go through a bunch of if-then statements to suss the logic out in one place, but what if you add other classes or races? Down this way lies spaghetti coding
So, let's take a look at getting around this via roles and via modules. In either case, it's BUILD to the rescue!
All that we need to know is which roles go with which combinations of class and race. This has to be hard coded somewhere, but this hard-coding will be the single, unambiguous, authoritative representation of these dependencies within our system.
So, if, for example, we've chosen a halfling rogue and we've stored the roles that we need to load in the array @roles (containing, say, Stat::Bonuses::Halfling, Allowable::Equipment::Small, Skills::Rogue, etc), then our BEGIN would look something like this:
And that's prettymuch it. If all goes well, the roles should be loaded. The only thing you need to watch for is to avoid duplicating method or attribute names within these roles, but that should be easy enough if you design them carefully.
The idea here is somewhat similar, but a bit different in that we're loading modules based upon our choices into attributes of our main class. If you want to call upon attributes and methods of each of these class/race related module within your main Character module, then you'll have to use handles.
Of course, it might be tempting to immediately pull down every method and attribute from each class via handles=>qr/.*/, but this is actually a bad idea since this also includes the Moose-specific magical BUILD and BUILDARGS methods. So, we need to be a bit more specific with our regex.
Also, to avoid confusion, it's a good idea to name the attributes differently from the corresponding module. So, let's assume that we've stored this information in the hash %modules (which might, for example, contain a key-value pair of "skills"=>"Skills::Rogue"). We'll also make use of Module::Load's load function:
Unfortunately, I've skipped over a couple of potentially important details....what if you want to pass arguments to the constructor of $module that were initially passed to the main class (this is definitely the case in Hypatia)? Then you'll have to sift through that information beforehand, either before the while loop above or separately in BUILDARGS. Also, what if you don't always want $attr to be read-only? Well, you'll have to suss out that information (including it in %modules, perhaps) and load that dynamically.
So, dependency injection in Moose can be done at run-time by either dynamically loading classes or by tying modules to attributes. The first approach seems simpler and is less likely to shatter the encapsulation-fourth-wall. However, it leaves all of the attributes and methods of all of the dynamically-loaded roles sitting in your primary class. The second approach does a better job of bucketing these attributes and methods, but can be much more difficult to set up if you have to pass arguments along (believe me...I've learned this the long and hard way).