| blib/lib/KelpX/Sweet.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % | 
| statement | 162 | 192 | 84.3 | 
| branch | 31 | 66 | 46.9 | 
| condition | 1 | 6 | 16.6 | 
| subroutine | 22 | 25 | 88.0 | 
| pod | 0 | 1 | 0.0 | 
| total | 216 | 290 | 74.4 | 
| line | stmt | bran | cond | sub | pod | time | code | 
|---|---|---|---|---|---|---|---|
| 1 | package KelpX::Sweet; | ||||||
| 2 | |||||||
| 3 | 7 | 7 | 25542 | use warnings; | |||
| 7 | 13 | ||||||
| 7 | 216 | ||||||
| 4 | 7 | 7 | 25 | use strict; | |||
| 7 | 9 | ||||||
| 7 | 171 | ||||||
| 5 | 7 | 7 | 2996 | use true; | |||
| 7 | 63192 | ||||||
| 7 | 41 | ||||||
| 6 | 7 | 7 | 10664 | use Text::ASCIITable; | |||
| 7 | 100706 | ||||||
| 7 | 486 | ||||||
| 7 | 7 | 7 | 4042 | use FindBin; | |||
| 7 | 6210 | ||||||
| 7 | 278 | ||||||
| 8 | 7 | 7 | 3145 | use Module::Find 'useall'; | |||
| 7 | 6710 | ||||||
| 7 | 385 | ||||||
| 9 | 7 | 7 | 37 | use base 'Kelp'; | |||
| 7 | 10 | ||||||
| 7 | 3981 | ||||||
| 10 | |||||||
| 11 | our $VERSION = '0.002'; | ||||||
| 12 | |||||||
| 13 | sub import { | ||||||
| 14 | 7 | 7 | 57 | my ($class, %opts) = @_; | |||
| 15 | 7 | 87 | strict->import(); | ||||
| 16 | 7 | 66 | warnings->import(); | ||||
| 17 | 7 | 43 | true->import(); | ||||
| 18 | 7 | 4660 | my $caller = caller; | ||||
| 19 | 7 | 11 | my $routes = []; | ||||
| 20 | 7 | 12 | my $configs = {}; | ||||
| 21 | 7 | 15 | my $auto = 0; | ||||
| 22 | { | ||||||
| 23 | 7 | 7 | 572241 | no strict 'refs'; | |||
| 7 | 16 | ||||||
| 7 | 8008 | ||||||
| 7 | 11 | ||||||
| 24 | 7 | 7 | push @{"${caller}::ISA"}, 'Kelp'; | ||||
| 7 | 73 | ||||||
| 25 | # check args | ||||||
| 26 | # auto load routes? | ||||||
| 27 | 7 | 50 | 26 | if ($opts{"-auto"}) { | |||
| 28 | 0 | 0 | $auto = 1; | ||||
| 29 | 0 | 0 | my $route_tb = Text::ASCIITable->new; | ||||
| 30 | 0 | 0 | $route_tb->setCols('Routes'); | ||||
| 31 | 0 | 0 | my @mod_routes = useall "${caller}::Route"; | ||||
| 32 | 0 | 0 | for my $mod (@mod_routes) { | ||||
| 33 | 0 | 0 | $route_tb->addRow($mod); | ||||
| 34 | 0 | 0 | push @$routes, $mod->get_routes(); | ||||
| 35 | } | ||||||
| 36 | |||||||
| 37 | 0 | 0 | print $route_tb . "\n"; | ||||
| 38 | } | ||||||
| 39 | |||||||
| 40 | 7 | 6 | 23 | *{"${caller}::new"} = sub { return shift->SUPER::new(@_); }; | |||
| 7 | 23 | ||||||
| 6 | 118 | ||||||
| 41 | 7 | 23 | *{"${caller}::maps"} = sub { | ||||
| 42 | 6 | 50 | 6 | 32 | die "Please don't use -auto and maps at the same time\n" | ||
| 43 | if $auto; | ||||||
| 44 | |||||||
| 45 | 6 | 11 | my ($route_names) = @_; | ||||
| 46 | 6 | 50 | 23 | unless (ref $route_names eq 'ARRAY') { | |||
| 47 | 0 | 0 | die "routes() expects an array references"; | ||||
| 48 | } | ||||||
| 49 | |||||||
| 50 | 6 | 68 | my $route_tb = Text::ASCIITable->new; | ||||
| 51 | 6 | 243 | $route_tb->setCols('Routes'); | ||||
| 52 | 6 | 615 | for my $mod (@$route_names) { | ||||
| 53 | 6 | 16 | my $route_path = "${caller}::Route::${mod}"; | ||||
| 54 | 6 | 6 | 445 | eval "use $route_path;"; | |||
| 6 | 2197 | ||||||
| 6 | 12 | ||||||
| 6 | 56 | ||||||
| 55 | 6 | 50 | 106 | if ($@) { | |||
| 56 | 0 | 0 | warn "Could not load route ${route_path}: $@"; | ||||
| 57 | 0 | 0 | next; | ||||
| 58 | } | ||||||
| 59 | |||||||
| 60 | 6 | 29 | $route_tb->addRow($route_path); | ||||
| 61 | 6 | 816 | push @$routes, $route_path->get_routes(); | ||||
| 62 | } | ||||||
| 63 | |||||||
| 64 | 6 | 26 | print $route_tb . "\n"; | ||||
| 65 | 7 | 26 | }; | ||||
| 66 | |||||||
| 67 | 7 | 19 | *{"${caller}::model"} = sub { | ||||
| 68 | 1 | 1 | 24675 | my ($self, $model) = @_; | |||
| 69 | 1 | 9 | return $self->{_models}->{$model}; | ||||
| 70 | 7 | 15 | }; | ||||
| 71 | |||||||
| 72 | 7 | 0 | 13 | *{"${caller}::path_to"} = sub { return $FindBin::Bin; }; | |||
| 7 | 19 | ||||||
| 0 | 0 | ||||||
| 73 | |||||||
| 74 | 7 | 20 | *{"${caller}::cfg"} = sub { | ||||
| 75 | 0 | 0 | 0 | my ($key, $hash) = @_; | |||
| 76 | 0 | 0 | $configs->{$key} = $hash; | ||||
| 77 | 7 | 16 | }; | ||||
| 78 | |||||||
| 79 | 7 | 27 | *{"${caller}::build"} = sub { | ||||
| 80 | 6 | 6 | 1023 | my ($self) = @_; | |||
| 81 | 6 | 27 | my $config = $self->config_hash; | ||||
| 82 | # config | ||||||
| 83 | 6 | 50 | 45 | if (scalar keys %$configs > 0) { | |||
| 84 | 0 | 0 | for my $key (keys %$configs) { | ||||
| 85 | 0 | 0 | $config->{"+${key}"} = $configs->{$key}; | ||||
| 86 | } | ||||||
| 87 | } | ||||||
| 88 | |||||||
| 89 | # models | ||||||
| 90 | 6 | 50 | 26 | if ($config->{models}) { | |||
| 91 | 6 | 16 | $self->{_models} = {}; | ||||
| 92 | 6 | 52 | my $model_tb = Text::ASCIITable->new; | ||||
| 93 | 6 | 271 | $model_tb->setCols('Model', 'Alias'); | ||||
| 94 | 6 | 50 | 618 | unless (ref $config->{models} eq 'HASH') { | |||
| 95 | 0 | 0 | die "config: models expects a hash reference\n"; | ||||
| 96 | } | ||||||
| 97 | |||||||
| 98 | 6 | 12 | for my $model (keys %{$config->{models}}) { | ||||
| 6 | 27 | ||||||
| 99 | 6 | 13 | my $name = $model; | ||||
| 100 | 6 | 13 | my $opts = $config->{models}->{$model}; | ||||
| 101 | 6 | 46 | my $mod = $opts->{model}; | ||||
| 102 | 6 | 6 | 586 | eval "use $mod;"; | |||
| 6 | 2204 | ||||||
| 6 | 813927 | ||||||
| 6 | 132 | ||||||
| 103 | 6 | 50 | 30 | if ($@) { | |||
| 104 | 0 | 0 | die "Could not load model $mod: $@"; | ||||
| 105 | } | ||||||
| 106 | |||||||
| 107 | 6 | 9 | my @args = @{$opts->{args}}; | ||||
| 6 | 31 | ||||||
| 108 | 6 | 50 | 30 | if (my $ret = $mod->build(@args)) { | |||
| 109 | 6 | 50 | 333710 | if (ref $ret) { | |||
| 110 | 6 | 52 | $model_tb->addRow($mod, $name); | ||||
| 111 | # returned a standard hash reference | ||||||
| 112 | 6 | 50 | 1017 | if (ref $ret eq 'HASH') { | |||
| 113 | 0 | 0 | foreach my $key (keys %$ret) { | ||||
| 114 | 0 | 0 | 0 | if (ref $ret->{$key}) { | |||
| 115 | 0 | 0 | $self->{_models}->{"${name}::${key}"} = $ret->{$key}; | ||||
| 116 | 0 | 0 | $model_tb->addRow(ref $ret->{$key}, "${name}::${key}"); | ||||
| 117 | } | ||||||
| 118 | } | ||||||
| 119 | } | ||||||
| 120 | else { | ||||||
| 121 | 6 | 26 | $self->{_models}->{$name} = $ret; | ||||
| 122 | |||||||
| 123 | # is this dbix::class? | ||||||
| 124 | 6 | 61 | require mro; | ||||
| 125 | 6 | 15 | my $dbref = ref $ret; | ||||
| 126 | 6 | 50 | 11 | if (grep { $_ eq 'DBIx::Class::Schema' } @{mro::get_linear_isa($dbref)}) { | |||
| 42 | 57 | ||||||
| 6 | 38 | ||||||
| 127 | 6 | 50 | 410 | if ($dbref->can('sources')) { | |||
| 128 | 6 | 35 | my @sources = $dbref->sources; | ||||
| 129 | 6 | 365 | for my $source (@sources) { | ||||
| 130 | 6 | 67 | $self->{_models}->{"${name}::${source}"} = $ret->resultset($source); | ||||
| 131 | 6 | 5575 | $model_tb->addRow("${dbref}::ResultSet::${source}", "${name}::${source}"); | ||||
| 132 | } | ||||||
| 133 | } | ||||||
| 134 | } | ||||||
| 135 | } | ||||||
| 136 | } | ||||||
| 137 | else { | ||||||
| 138 | 0 | 0 | die "Did not return a valid object from models build(): $name\n"; | ||||
| 139 | } | ||||||
| 140 | } | ||||||
| 141 | else { | ||||||
| 142 | 0 | 0 | die "build() failed: $mod"; | ||||
| 143 | } | ||||||
| 144 | } | ||||||
| 145 | |||||||
| 146 | 6 | 50 | 737 | if (scalar keys %{$self->{_models}} > 0) { | |||
| 6 | 40 | ||||||
| 147 | 6 | 30 | print $model_tb . "\n"; | ||||
| 148 | } | ||||||
| 149 | } | ||||||
| 150 | # routes | ||||||
| 151 | 6 | 7843 | my $r = $self->routes; | ||||
| 152 | 6 | 45 | for my $route (@$routes) { | ||||
| 153 | 6 | 36 | for my $url (keys %$route) { | ||||
| 154 | 36 | 100 | 18355 | if ($route->{$url}->{bridge}) { | |||
| 50 | |||||||
| 155 | 6 | 56 | $r->add([ uc($route->{$url}->{type}) => $url ], { to => $route->{$url}->{coderef}, bridge => 1 }); | ||||
| 156 | } | ||||||
| 157 | elsif ($route->{$url}->{type} eq 'any') { | ||||||
| 158 | 0 | 0 | $r->add($url, $route->{$url}->{coderef}); | ||||
| 159 | } | ||||||
| 160 | else { | ||||||
| 161 | 30 | 191 | $r->add([ uc($route->{$url}->{type}) => $url ], $route->{$url}->{coderef}); | ||||
| 162 | } | ||||||
| 163 | } | ||||||
| 164 | } | ||||||
| 165 | 7 | 37 | }; | ||||
| 166 | |||||||
| 167 | 7 | 18 | *{"${caller}::detach"} = sub { | ||||
| 168 | 1 | 1 | 30507 | my ($self) = @_; | |||
| 169 | |||||||
| 170 | 1 | 12 | my @caller = caller(1); | ||||
| 171 | 1 | 3 | my $fullpath = $caller[3]; | ||||
| 172 | 1 | 1 | my $name; | ||||
| 173 | 1 | 50 | 9 | if ($fullpath =~ /.+::(.+)$/) { | |||
| 174 | 1 | 4 | $name = $1; | ||||
| 175 | } | ||||||
| 176 | |||||||
| 177 | 1 | 50 | 3 | if ($name) { | |||
| 178 | 1 | 50 | 3 | print "[debug] Rendering template: $name\n" if $ENV{KELPX_SWEET_DEBUG}; | |||
| 179 | 1 | 4 | $self->template($name, $self->stash); | ||||
| 180 | } | ||||||
| 181 | 7 | 16 | }; | ||||
| 182 | |||||||
| 183 | # if 'has' is not available (ie: no Moose, Moo, Mouse, etc), then import our own small version | ||||||
| 184 | 7 | 50 | 82 | unless ($caller->can('has')) { | |||
| 185 | 7 | 11 | *{"${caller}::has"} = \&_has; | ||||
| 7 | 14 | ||||||
| 186 | } | ||||||
| 187 | |||||||
| 188 | # if 'around' is not available, import a small version of our own | ||||||
| 189 | { | ||||||
| 190 | 7 | 7 | 41 | no warnings 'redefine'; | |||
| 7 | 11 | ||||||
| 7 | 1766 | ||||||
| 7 | 10 | ||||||
| 191 | 7 | 50 | 49 | unless ($caller->can('around')) { | |||
| 192 | 7 | 809 | *{"${caller}::around"} = sub { | ||||
| 193 | 6 | 6 | 3112 | my ($method, $code) = @_; | |||
| 194 | |||||||
| 195 | 6 | 17 | my $fullpkg = "${caller}::${method}"; | ||||
| 196 | 6 | 17 | my $old_code = \&$fullpkg; | ||||
| 197 | 6 | 87 | *{"${fullpkg}"} = sub { | ||||
| 198 | 6 | 6 | 401586 | $code->($old_code, @_); | |||
| 199 | 6 | 18 | }; | ||||
| 200 | 7 | 17 | }; | ||||
| 201 | } | ||||||
| 202 | } | ||||||
| 203 | } | ||||||
| 204 | } | ||||||
| 205 | |||||||
| 206 | sub _has { | ||||||
| 207 | 6 | 6 | 51 | my ($acc, %attrs) = @_; | |||
| 208 | 6 | 12 | my $class = caller; | ||||
| 209 | 6 | 8 | my $ro = 0; | ||||
| 210 | 6 | 10 | my $rq = 0; | ||||
| 211 | 6 | 7 | my $df; | ||||
| 212 | 6 | 50 | 16 | if (%attrs) { | |||
| 213 | 6 | 50 | 18 | if ($attrs{is} eq 'ro') { $ro = 1; } | |||
| 6 | 11 | ||||||
| 214 | 6 | 50 | 14 | if ($attrs{required}) { $rq = 1; } | |||
| 6 | 10 | ||||||
| 215 | 6 | 50 | 15 | if ($attrs{default}) { $df = $attrs{default}; } | |||
| 6 | 7 | ||||||
| 216 | |||||||
| 217 | 6 | 50 | 19 | if ($df) { | |||
| 218 | 6 | 50 | 22 | die "has: default expects a code reference\n" | |||
| 219 | unless ref $df eq 'CODE'; | ||||||
| 220 | } | ||||||
| 221 | } | ||||||
| 222 | |||||||
| 223 | { | ||||||
| 224 | 7 | 7 | 97 | no strict 'refs'; | |||
| 7 | 9 | ||||||
| 7 | 1839 | ||||||
| 6 | 9 | ||||||
| 225 | 6 | 42 | *{"${class}::${acc}"} = sub { | ||||
| 226 | #if ($attrs{default}) { $_[0]->{$name} = $attrs{default}; } | ||||||
| 227 | 1 | 50 | 33 | 1 | 21121 | if ($rq and not $df) { | |
| 228 | 0 | 0 | 0 | 0 | if (not $_[0]->{$acc} and not $_[1]) { | ||
| 229 | 0 | 0 | die "You attempted to use a field that can't be left blank: ${acc}"; | ||||
| 230 | } | ||||||
| 231 | } | ||||||
| 232 | |||||||
| 233 | 1 | 50 | 4 | if ($df) { $_[0]->{$acc} = $df->(); } | |||
| 1 | 3 | ||||||
| 234 | |||||||
| 235 | 1 | 50 | 7 | if (@_ == 2) { | |||
| 236 | 0 | 0 | 0 | die "Can't modify a readonly accessor: ${acc}" | |||
| 237 | if $ro; | ||||||
| 238 | 0 | 0 | $_[0]->{$acc} = $_[1]; | ||||
| 239 | } | ||||||
| 240 | 1 | 4 | return $_[0]->{$acc}; | ||||
| 241 | 6 | 22 | }; | ||||
| 242 | } | ||||||
| 243 | } | ||||||
| 244 | |||||||
| 245 | sub new { | ||||||
| 246 | 0 | 0 | 0 | 0 | bless { @_[ 1 .. $#_ ] }, $_[0]; | ||
| 247 | } | ||||||
| 248 | |||||||
| 249 | =head1 NAME | ||||||
| 250 | |||||||
| 251 | KelpX::Sweet - Kelp with extra sweeteners | ||||||
| 252 | |||||||
| 253 | =head1 DESCRIPTION | ||||||
| 254 | |||||||
| 255 | Kelp is good. Kelp is great. But what if you could give it more syntactic sugar and separate your routes from the logic in a cleaner way? KelpX::Sweet attempts to do just that. | ||||||
| 256 | |||||||
| 257 | =head1 SIMPLE TUTORIAL | ||||||
| 258 | |||||||
| 259 | For the most part, your original C | ||||||
| 260 | |||||||
| 261 | B | ||||||
| 262 | |||||||
| 263 | package MyApp; | ||||||
| 264 | use KelpX::Sweet; | ||||||
| 265 | |||||||
| 266 | maps ['Main']; | ||||||
| 267 | |||||||
| 268 | Yep, that's the complete code for your base. You pass C | ||||||
| 269 | It will look for them in C | ||||||
| 270 | Next, let's create that file | ||||||
| 271 | |||||||
| 272 | B | ||||||
| 273 | |||||||
| 274 | package MyApp::Route::Main; | ||||||
| 275 | |||||||
| 276 | use KelpX::Sweet::Route; | ||||||
| 277 | |||||||
| 278 | get '/' => 'Controller::Root::hello'; | ||||||
| 279 | get '/nocontroller' => sub { 'Hello, world from no controller!' }; | ||||||
| 280 | |||||||
| 281 | Simply use C | ||||||
| 282 | but that makes the whole idea of this module pointless ;) | ||||||
| 283 | It will load C | ||||||
| 284 | any of your arguments will also be sent the method inside that controller, so you don't need to do anything else! | ||||||
| 285 | |||||||
| 286 | Finally, we can create the controller | ||||||
| 287 | |||||||
| 288 | B | ||||||
| 289 | |||||||
| 290 | package MyApp::Controller::Root; | ||||||
| 291 | |||||||
| 292 | use KelpX::Sweet::Controller; | ||||||
| 293 | |||||||
| 294 | sub hello { | ||||||
| 295 | my ($self) = @_; | ||||||
| 296 | return "Hello, world!"; | ||||||
| 297 | } | ||||||
| 298 | |||||||
| 299 | You now have a fully functional Kelp app! Remember, because this module is just a wrapper, you can do pretty much anything L | ||||||
| 300 | can, like C<$self->>param> for example. | ||||||
| 301 | |||||||
| 302 | =head1 SUGARY SYNTAX | ||||||
| 303 | |||||||
| 304 | By sugar, we mean human readable and easy to use. You no longer need a build method, then to call ->add on an object for your | ||||||
| 305 | routes. It uses a similar syntax to L | ||||||
| 306 | |||||||
| 307 | =head2 get | ||||||
| 308 | |||||||
| 309 | This will trigger a standard GET request. | ||||||
| 310 | |||||||
| 311 | get '/mypage' => sub { 'It works' }; | ||||||
| 312 | |||||||
| 313 | =head2 post | ||||||
| 314 | |||||||
| 315 | Will trigger on POST requests only | ||||||
| 316 | |||||||
| 317 | post '/someform' => sub { 'Posted like a boss' }; | ||||||
| 318 | |||||||
| 319 | =head2 any | ||||||
| 320 | |||||||
| 321 | Will trigger on POST B | ||||||
| 322 | |||||||
| 323 | any '/omni' => sub { 'Hit me up on any request' }; | ||||||
| 324 | |||||||
| 325 | =head2 bridge | ||||||
| 326 | |||||||
| 327 | Bridges are cool, so please check out the Kelp documentation for more information on what they do and how they work. | ||||||
| 328 | |||||||
| 329 | bridge '/users/:id' => sub { | ||||||
| 330 | unless ($self->user->logged_in) { | ||||||
| 331 | return; | ||||||
| 332 | } | ||||||
| 333 | |||||||
| 334 | return 1; | ||||||
| 335 | }; | ||||||
| 336 | |||||||
| 337 | get '/users/:id/view' => 'Controller::Users::view'; | ||||||
| 338 | |||||||
| 339 | =head2 has | ||||||
| 340 | |||||||
| 341 | If you only want basic accessors and KelpX::Sweet detects you don't have any OOP frameworks activated with C | ||||||
| 342 | own little method which works similar to L | ||||||
| 343 | |||||||
| 344 | package MyApp; | ||||||
| 345 | |||||||
| 346 | use KelpX::Sweet; | ||||||
| 347 | has 'x' => ( is => 'rw', default => sub { "Hello, world" } ); | ||||||
| 348 | |||||||
| 349 | package MyApp::Controller::Main; | ||||||
| 350 | |||||||
| 351 | use KelpX::Sweet::Controller; | ||||||
| 352 | |||||||
| 353 | sub hello { shift->x; } # Hello, world | ||||||
| 354 | |||||||
| 355 | =head2 around | ||||||
| 356 | |||||||
| 357 | Need more power? Want to modify the default C | ||||||
| 358 | This allows you to tap into build if you really want to for some reason. | ||||||
| 359 | |||||||
| 360 | package MyApp; | ||||||
| 361 | |||||||
| 362 | use KelpX::Sweet; | ||||||
| 363 | |||||||
| 364 | around 'build' => sub { | ||||||
| 365 | my $method = shift; | ||||||
| 366 | my $self = shift; | ||||||
| 367 | my $routes = $self->routes; | ||||||
| 368 | $routes->add('/manual' => sub { "Manually added" }); | ||||||
| 369 | |||||||
| 370 | $self->$method(@_); | ||||||
| 371 | }; | ||||||
| 372 | |||||||
| 373 | =head1 MODELS | ||||||
| 374 | |||||||
| 375 | You can always use an attribute to create a database connection, or separate them using models in a slightly cleaner way. | ||||||
| 376 | In your config you supply a hash reference with the models alias (what you will reference it as in code), the full path, and finally any | ||||||
| 377 | arguments it might have (like the dbi line, username and password). | ||||||
| 378 | |||||||
| 379 | # config.pl | ||||||
| 380 | models => { | ||||||
| 381 | 'LittleDB' => { | ||||||
| 382 | 'model' => 'TestApp::Model::LittleDB', | ||||||
| 383 | 'args' => ['dbi:SQLite:testapp.db'], | ||||||
| 384 | }, | ||||||
| 385 | }, | ||||||
| 386 | |||||||
| 387 | Then, you create C | ||||||
| 388 | |||||||
| 389 | package TestApp::Model::LittleDB; | ||||||
| 390 | |||||||
| 391 | use KelpX::Sweet::Model; | ||||||
| 392 | use DBIx::Lite; | ||||||
| 393 | |||||||
| 394 | sub build { | ||||||
| 395 | my ($self, @args) = @_; | ||||||
| 396 | return DBIx::Lite->connect(@args); | ||||||
| 397 | } | ||||||
| 398 | |||||||
| 399 | As you can see, the C | ||||||
| 400 | |||||||
| 401 | That's all you need. Now you can pull that model instance out at any time in your controllers with C | ||||||
| 402 | |||||||
| 403 | package TestApp::Controller::User; | ||||||
| 404 | |||||||
| 405 | use KelpX::Sweet::Controller; | ||||||
| 406 | |||||||
| 407 | sub users { | ||||||
| 408 | my ($self) = @_; | ||||||
| 409 | my @users = $self->model('LittleDB')->table('users')->all; | ||||||
| 410 | return join ', ', map { $_->name } @users; | ||||||
| 411 | } | ||||||
| 412 | |||||||
| 413 | =head2 Named ResultSets | ||||||
| 414 | |||||||
| 415 | If you're not using DBIx::Class, you can still have similar styled resultsets. Simply return a standard hash reference instead of a blessed object | ||||||
| 416 | from the C | ||||||
| 417 | |||||||
| 418 | package TestApp::Model::LittleDB; | ||||||
| 419 | |||||||
| 420 | use KelpX::Sweet::Model; | ||||||
| 421 | use DBIx::Lite; | ||||||
| 422 | |||||||
| 423 | sub build { | ||||||
| 424 | my ($self, @args) = @_; | ||||||
| 425 | my $schema = DBIx::Lite->connect(@args); | ||||||
| 426 | return { | ||||||
| 427 | 'User' => $schema->table('users'), | ||||||
| 428 | 'Product' => $schema->table('products'), | ||||||
| 429 | }; | ||||||
| 430 | } | ||||||
| 431 | |||||||
| 432 | Then, you can do this stuff in your controllers | ||||||
| 433 | |||||||
| 434 | package TestApp::Controller::Assets; | ||||||
| 435 | |||||||
| 436 | sub users { | ||||||
| 437 | my ($self) = @_; | ||||||
| 438 | my @users = $self->model('LittleDB::User')->all; | ||||||
| 439 | return join " ", map { $_->name . " (" . $_->email . ")" } @users; | ||||||
| 440 | } | ||||||
| 441 | |||||||
| 442 | sub products { | ||||||
| 443 | my ($self) = @_; | ||||||
| 444 | my @products = $self->model('LittleDB::Product')->all; | ||||||
| 445 | return join " ", map { $_->name . " (" . sprintf("%.2f", $_->value) . ")" } @products; | ||||||
| 446 | } | ||||||
| 447 | |||||||
| 448 | =head2 Models and DBIx::Class | ||||||
| 449 | |||||||
| 450 | If you enjoy the way Catalyst handles DBIx::Class models, you're going to love this (I hope so, at least). KelpX::Sweet will automagically | ||||||
| 451 | create models based on the sources of your schema if it detects it's a DBIx::Class::Schema. | ||||||
| 452 | Nothing really has to change, KelpX::Sweet will figure it out on its own. | ||||||
| 453 | |||||||
| 454 | package TestApp::Model::LittleDB; | ||||||
| 455 | |||||||
| 456 | use KelpX::Sweet::Model; | ||||||
| 457 | use LittleDB::Schema; | ||||||
| 458 | |||||||
| 459 | sub build { | ||||||
| 460 | my ($self, @args) = @_; | ||||||
| 461 | return LittleDB::Schema->connect(@args); | ||||||
| 462 | } | ||||||
| 463 | |||||||
| 464 | Then just use it as you normally would in Catalyst (except we store it in C<$self>, not C<$c>). | ||||||
| 465 | |||||||
| 466 | package TestApp::Controller::User; | ||||||
| 467 | |||||||
| 468 | use KelpX::Sweet::Controller; | ||||||
| 469 | |||||||
| 470 | sub users { | ||||||
| 471 | my ($self) = @_; | ||||||
| 472 | my @users = $self->model('LittleDB::User')->all; | ||||||
| 473 | return join ', ', map { $_->name } @users; | ||||||
| 474 | } | ||||||
| 475 | |||||||
| 476 | KelpX::Sweet will loop through all your schemas sources and create models based on your alias, and the sources name. So, C | ||||||
| 477 | |||||||
| 478 | When we start our app, even though we've only added LittleDB, you'll see we have the new ones based on our Schema. Neat! | ||||||
| 479 | |||||||
| 480 | .----------------------------------------------------------. | ||||||
| 481 | | Model | Alias | | ||||||
| 482 | +--------------------------------------+-------------------+ | ||||||
| 483 | | TestApp::Model::LittleDB | LittleDB | | ||||||
| 484 | | LittleDB::Schema::ResultSet::User | LittleDB::User | | ||||||
| 485 | | LittleDB::Schema::ResultSet::Product | LittleDB::Product | | ||||||
| 486 | '--------------------------------------+-------------------' | ||||||
| 487 | |||||||
| 488 | =head1 VIEWS | ||||||
| 489 | |||||||
| 490 | OK, so to try and not separate too much, I've chosen not to include views. Just use the standard Kelp modules | ||||||
| 491 | (ie: L | ||||||
| 492 | |||||||
| 493 | =head2 detach | ||||||
| 494 | |||||||
| 495 | This method will call C for you with the added benefit of automatically filling out the filename and including whatever | ||||||
| 496 | is in the stash for you. | ||||||
| 497 | |||||||
| 498 | package MyApp::Controller::Awesome; | ||||||
| 499 | |||||||
| 500 | use KelpX::Sweet::Controller; | ||||||
| 501 | |||||||
| 502 | sub hello { | ||||||
| 503 | my ($self) = @_; | ||||||
| 504 | $self->stash->{name} = 'World'; | ||||||
| 505 | $self->detach; | ||||||
| 506 | } | ||||||
| 507 | |||||||
| 508 | Then, you just create C | ||||||
| 509 | |||||||
| 510 | Hello, [% name %] | ||||||
| 511 | |||||||
| 512 | While not really required, it does save a bit of typing and can come in quite useful. | ||||||
| 513 | |||||||
| 514 | =head1 IMPORT OPTIONS | ||||||
| 515 | |||||||
| 516 | =head2 -auto | ||||||
| 517 | |||||||
| 518 | Importing -auto will automatically include any route modules within your C | ||||||
| 519 | For example, we have two controllers, C | ||||||
| 520 | |||||||
| 521 | package MyApp::Route::Main; | ||||||
| 522 | |||||||
| 523 | use KelpX::Sweet::Route; | ||||||
| 524 | |||||||
| 525 | get '/' => sub { "Hi" }; | ||||||
| 526 | |||||||
| 527 | package MyApp::Route::New; | ||||||
| 528 | |||||||
| 529 | use KelpX::Sweet::Route; | ||||||
| 530 | |||||||
| 531 | get '/new/url' => sub { "New one" }; | ||||||
| 532 | |||||||
| 533 | Then to kick off our app, all we need is | ||||||
| 534 | |||||||
| 535 | package MyApp; | ||||||
| 536 | use KelpX::Sweet -auto => 1; | ||||||
| 537 | |||||||
| 538 | That's it. KelpX::Sweet will complain if you attempt to use C | ||||||
| 539 | |||||||
| 540 | =head1 REALLY COOL THINGS TO NOTE | ||||||
| 541 | |||||||
| 542 | =head2 Default imports | ||||||
| 543 | |||||||
| 544 | You should be aware that KelpX::Sweet will import warnings, strict and true for you. Because of this, there is no requirement to | ||||||
| 545 | add a true value to the end of your file. I chose this because it just makes things look a little cleaner. | ||||||
| 546 | |||||||
| 547 | =head2 KelpX::Sweet starter | ||||||
| 548 | |||||||
| 549 | On installation of KelpX::Sweet, you'll receive a file called C | ||||||
| 550 | and it will create a working test app with minimal boilerplate so you can get started straight away. Just run it as: | ||||||
| 551 | |||||||
| 552 | $ kelpx-sweet MyApp | ||||||
| 553 | $ kelpx-sweet Something::With::A::Larger::Namespace | ||||||
| 554 | |||||||
| 555 | =head1 AUTHOR | ||||||
| 556 | |||||||
| 557 | Brad Haywood | ||||||
| 558 | |||||||
| 559 | =head1 LICENSE | ||||||
| 560 | |||||||
| 561 | You may distribute this code under the same terms as Perl itself. | ||||||
| 562 | |||||||
| 563 | =cut | ||||||
| 564 | |||||||
| 565 | 1; | ||||||
| 566 | __END__ |