blib/lib/Pulp.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 66 | 226 | 29.2 |
branch | 5 | 74 | 6.7 |
condition | 1 | 9 | 11.1 |
subroutine | 11 | 26 | 42.3 |
pod | 0 | 1 | 0.0 |
total | 83 | 336 | 24.7 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Pulp; | ||||||
2 | |||||||
3 | 1 | 1 | 24982 | use warnings; | |||
1 | 3 | ||||||
1 | 45 | ||||||
4 | 1 | 1 | 8 | use strict; | |||
1 | 2 | ||||||
1 | 57 | ||||||
5 | 1 | 1 | 642 | use true; | |||
1 | 26494 | ||||||
1 | 5 | ||||||
6 | 1 | 1 | 1894 | use Text::ASCIITable; | |||
1 | 17418 | ||||||
1 | 66 | ||||||
7 | 1 | 1 | 580 | use FindBin; | |||
1 | 950 | ||||||
1 | 81 | ||||||
8 | 1 | 1 | 450 | use Module::Find 'useall'; | |||
1 | 1239 | ||||||
1 | 73 | ||||||
9 | 1 | 1 | 6 | use base 'Kelp'; | |||
1 | 1 | ||||||
1 | 775 | ||||||
10 | |||||||
11 | our $VERSION = '0.001'; | ||||||
12 | |||||||
13 | sub import { | ||||||
14 | 1 | 1 | 10 | my ($class, %opts) = @_; | |||
15 | 1 | 11 | strict->import(); | ||||
16 | 1 | 11 | warnings->import(); | ||||
17 | 1 | 6 | true->import(); | ||||
18 | 1 | 741 | my $caller = caller; | ||||
19 | 1 | 2 | my $routes = []; | ||||
20 | 1 | 1 | my $configs = {}; | ||||
21 | 1 | 11 | my $auto = 0; | ||||
22 | { | ||||||
23 | 1 | 1 | 92019 | no strict 'refs'; | |||
1 | 3 | ||||||
1 | 1532 | ||||||
1 | 1 | ||||||
24 | 1 | 1 | push @{"${caller}::ISA"}, 'Kelp'; | ||||
1 | 10 | ||||||
25 | # check args | ||||||
26 | # v2 | ||||||
27 | 1 | 50 | 4 | if ($opts{"-v2"}) { | |||
28 | 0 | 0 | my $con_tb = Text::ASCIITable->new({ headingText => 'Controllers' }); | ||||
29 | 0 | 0 | $con_tb->setCols('Action', 'Path'); | ||||
30 | 0 | 0 | my @conts = useall "${caller}::Controller"; | ||||
31 | 0 | 0 | for my $mod (@conts) { | ||||
32 | 0 | 0 | my $actions = $mod->_actions; | ||||
33 | 0 | 0 | foreach my $action (keys %$actions) { | ||||
34 | 0 | 0 | $con_tb->addRow($action, $actions->{$action}); | ||||
35 | } | ||||||
36 | } | ||||||
37 | |||||||
38 | 0 | 0 | print $con_tb . "\n"; | ||||
39 | } | ||||||
40 | |||||||
41 | # auto load routes? | ||||||
42 | 1 | 2 | my @in_mods; | ||||
43 | 1 | 50 | 2 | if ($opts{"extends"}) { | |||
44 | 0 | 0 | @in_mods = useall $opts{"extends"} . "::Route"; | ||||
45 | } | ||||||
46 | |||||||
47 | 1 | 50 | 33 | 5 | if ($opts{"-auto"} || $opts{"extends"}) { | ||
48 | 0 | 0 | $auto = 1; | ||||
49 | 0 | 0 | my $route_tb = Text::ASCIITable->new; | ||||
50 | 0 | 0 | $route_tb->setCols('Routes'); | ||||
51 | 0 | 0 | my @mod_routes = useall "${caller}::Route"; | ||||
52 | 0 | 0 | for my $mod (@mod_routes) { | ||||
53 | 0 | 0 | $route_tb->addRow($mod); | ||||
54 | 0 | 0 | push @$routes, $mod->get_routes(); | ||||
55 | } | ||||||
56 | |||||||
57 | 0 | 0 | 0 | if (@in_mods) { | |||
58 | 0 | 0 | for my $mod (@in_mods) { | ||||
59 | 0 | 0 | $route_tb->addRow($mod); | ||||
60 | 0 | 0 | push @$routes, $mod->get_routes(); | ||||
61 | } | ||||||
62 | } | ||||||
63 | |||||||
64 | 0 | 0 | print $route_tb . "\n"; | ||||
65 | } | ||||||
66 | |||||||
67 | 1 | 0 | 3 | *{"${caller}::new"} = sub { return shift->SUPER::new(@_); }; | |||
1 | 9 | ||||||
0 | 0 | ||||||
68 | 1 | 4 | *{"${caller}::maps"} = sub { | ||||
69 | 0 | 0 | 0 | 0 | die "Please don't use -auto and maps at the same time\n" | ||
70 | if $auto; | ||||||
71 | |||||||
72 | 0 | 0 | my ($route_names) = @_; | ||||
73 | 0 | 0 | 0 | unless (ref $route_names eq 'ARRAY') { | |||
74 | 0 | 0 | die "routes() expects an array references"; | ||||
75 | } | ||||||
76 | |||||||
77 | 0 | 0 | my $route_tb = Text::ASCIITable->new; | ||||
78 | 0 | 0 | $route_tb->setCols('Routes'); | ||||
79 | 0 | 0 | for my $mod (@$route_names) { | ||||
80 | 0 | 0 | my $route_path = "${caller}::Route::${mod}"; | ||||
81 | 0 | 0 | eval "use $route_path;"; | ||||
82 | 0 | 0 | 0 | if ($@) { | |||
83 | 0 | 0 | warn "Could not load route ${route_path}: $@"; | ||||
84 | 0 | 0 | next; | ||||
85 | } | ||||||
86 | |||||||
87 | 0 | 0 | $route_tb->addRow($route_path); | ||||
88 | 0 | 0 | push @$routes, $route_path->get_routes(); | ||||
89 | } | ||||||
90 | |||||||
91 | 0 | 0 | print $route_tb . "\n"; | ||||
92 | 1 | 2 | }; | ||||
93 | |||||||
94 | 1 | 3 | *{"${caller}::model"} = sub { | ||||
95 | 0 | 0 | 0 | my ($self, $model) = @_; | |||
96 | 0 | 0 | return $self->{_models}->{$model}; | ||||
97 | 1 | 1 | }; | ||||
98 | |||||||
99 | 1 | 0 | 2 | *{"${caller}::path_to"} = sub { return $FindBin::Bin; }; | |||
1 | 3 | ||||||
0 | 0 | ||||||
100 | |||||||
101 | 1 | 3 | *{"${caller}::cfg"} = sub { | ||||
102 | 0 | 0 | 0 | my ($key, $hash) = @_; | |||
103 | 0 | 0 | $configs->{$key} = $hash; | ||||
104 | 1 | 1 | }; | ||||
105 | |||||||
106 | 1 | 2 | *{"${caller}::build"} = sub { | ||||
107 | 0 | 0 | 0 | my ($self) = @_; | |||
108 | 0 | 0 | my $config = $self->config_hash; | ||||
109 | # config | ||||||
110 | 0 | 0 | 0 | if (scalar keys %$configs > 0) { | |||
111 | 0 | 0 | for my $key (keys %$configs) { | ||||
112 | 0 | 0 | $config->{"+${key}"} = $configs->{$key}; | ||||
113 | } | ||||||
114 | } | ||||||
115 | |||||||
116 | # models | ||||||
117 | 0 | 0 | 0 | if ($config->{models}) { | |||
118 | 0 | 0 | $self->{_models} = {}; | ||||
119 | 0 | 0 | my $model_tb = Text::ASCIITable->new; | ||||
120 | 0 | 0 | $model_tb->setCols('Model', 'Alias'); | ||||
121 | 0 | 0 | 0 | unless (ref $config->{models} eq 'HASH') { | |||
122 | 0 | 0 | die "config: models expects a hash reference\n"; | ||||
123 | } | ||||||
124 | |||||||
125 | 0 | 0 | for my $model (keys %{$config->{models}}) { | ||||
0 | 0 | ||||||
126 | 0 | 0 | my $name = $model; | ||||
127 | 0 | 0 | my $opts = $config->{models}->{$model}; | ||||
128 | 0 | 0 | my $mod = $opts->{model}; | ||||
129 | 0 | 0 | eval "use $mod;"; | ||||
130 | 0 | 0 | 0 | if ($@) { | |||
131 | 0 | 0 | die "Could not load model $mod: $@"; | ||||
132 | } | ||||||
133 | |||||||
134 | 0 | 0 | my @args = @{$opts->{args}}; | ||||
0 | 0 | ||||||
135 | 0 | 0 | 0 | if (my $ret = $mod->build(@args)) { | |||
136 | 0 | 0 | 0 | if (ref $ret) { | |||
137 | 0 | 0 | $model_tb->addRow($mod, $name); | ||||
138 | # returned a standard hash reference | ||||||
139 | 0 | 0 | 0 | if (ref $ret eq 'HASH') { | |||
140 | 0 | 0 | foreach my $key (keys %$ret) { | ||||
141 | 0 | 0 | 0 | if (ref $ret->{$key}) { | |||
142 | 0 | 0 | $self->{_models}->{"${name}::${key}"} = $ret->{$key}; | ||||
143 | 0 | 0 | $model_tb->addRow(ref $ret->{$key}, "${name}::${key}"); | ||||
144 | } | ||||||
145 | } | ||||||
146 | } | ||||||
147 | else { | ||||||
148 | 0 | 0 | $self->{_models}->{"${name}"} = $ret; | ||||
149 | |||||||
150 | # is this dbix::class? | ||||||
151 | 0 | 0 | require mro; | ||||
152 | 0 | 0 | my $dbref = ref $ret; | ||||
153 | 0 | 0 | 0 | if (grep { $_ eq 'DBIx::Class::Schema' } @{mro::get_linear_isa($dbref)}) { | |||
0 | 0 | ||||||
0 | 0 | ||||||
154 | 0 | 0 | 0 | if ($dbref->can('sources')) { | |||
155 | 0 | 0 | my $use_api = $mod->_use_api; | ||||
156 | 0 | 0 | my @sources = $dbref->sources; | ||||
157 | 0 | 0 | for my $source (@sources) { | ||||
158 | 0 | 0 | $self->{_models}->{"${name}::${source}"} = $ret->resultset($source); | ||||
159 | 0 | 0 | $model_tb->addRow("${dbref}::ResultSet::${source}", "${name}::${source}"); | ||||
160 | |||||||
161 | 0 | 0 | 0 | if ($use_api) { | |||
162 | 0 | 0 | my $lc_source = lc $source; | ||||
163 | $self->routes->add(['GET' => "/api/${lc_source}/list" ], { to => sub { | ||||||
164 | 0 | 0 | 0 | my ($aself) = @_; | |||
165 | 0 | 0 | my @data; | ||||
166 | 0 | 0 | my @users = $aself->model("${name}::${source}")->all; | ||||
167 | 0 | 0 | for my $user (@users) { | ||||
168 | 0 | 0 | my %usr_data = $user->get_columns; | ||||
169 | 0 | 0 | push @data, \%usr_data; | ||||
170 | } | ||||||
171 | |||||||
172 | 0 | 0 | return \@data; | ||||
173 | 0 | 0 | }}); | ||||
174 | |||||||
175 | $self->routes->add(['GET' => "/api/${lc_source}/find/:id"], { to => sub { | ||||||
176 | 0 | 0 | 0 | my ($aself, $id) = @_; | |||
177 | 0 | 0 | my %data = $aself->model("${name}::${source}")->find($id)->get_columns; | ||||
178 | 0 | 0 | return \%data; | ||||
179 | 0 | 0 | }}); | ||||
180 | |||||||
181 | $self->routes->add(['GET' => "/api/${lc_source}/search"], { to => sub { | ||||||
182 | 0 | 0 | 0 | my ($aself) = @_; | |||
183 | 0 | 0 | my @users = $aself->model("${name}::${source}") | ||||
184 | 0 | 0 | ->search({ map { $_ => $aself->param($_) } $aself->param }); | ||||
185 | 0 | 0 | my @data; | ||||
186 | 0 | 0 | for my $user (@users) { | ||||
187 | 0 | 0 | my %usr_data = $user->get_columns; | ||||
188 | 0 | 0 | push @data, \%usr_data; | ||||
189 | } | ||||||
190 | |||||||
191 | 0 | 0 | return \@data; | ||||
192 | 0 | 0 | }}); | ||||
193 | } | ||||||
194 | } | ||||||
195 | } | ||||||
196 | } | ||||||
197 | } | ||||||
198 | } | ||||||
199 | else { | ||||||
200 | 0 | 0 | die "Did not return a valid object from models build(): $name\n"; | ||||
201 | } | ||||||
202 | } | ||||||
203 | else { | ||||||
204 | 0 | 0 | die "build() failed: $mod"; | ||||
205 | } | ||||||
206 | } | ||||||
207 | |||||||
208 | 0 | 0 | 0 | if (scalar keys %{$self->{_models}} > 0) { | |||
0 | 0 | ||||||
209 | 0 | 0 | print $model_tb . "\n"; | ||||
210 | } | ||||||
211 | } | ||||||
212 | # routes | ||||||
213 | 0 | 0 | my $r = $self->routes; | ||||
214 | 0 | 0 | for my $route (@$routes) { | ||||
215 | 0 | 0 | for my $url (keys %$route) { | ||||
216 | 0 | 0 | 0 | if ($route->{$url}->{bridge}) { | |||
0 | |||||||
217 | 0 | 0 | $r->add([ uc($route->{$url}->{type}) => $url ], { to => $route->{$url}->{coderef}, bridge => 1 }); | ||||
218 | } | ||||||
219 | elsif ($route->{$url}->{type} eq 'any') { | ||||||
220 | 0 | 0 | $r->add($url, $route->{$url}->{coderef}); | ||||
221 | } | ||||||
222 | else { | ||||||
223 | 0 | 0 | $r->add([ uc($route->{$url}->{type}) => $url ], $route->{$url}->{coderef}); | ||||
224 | } | ||||||
225 | } | ||||||
226 | } | ||||||
227 | 1 | 4 | }; | ||||
228 | |||||||
229 | 1 | 3 | *{"${caller}::detach"} = sub { | ||||
230 | 0 | 0 | 0 | my ($self) = @_; | |||
231 | |||||||
232 | 0 | 0 | my @caller = caller(1); | ||||
233 | 0 | 0 | my $fullpath = $caller[3]; | ||||
234 | 0 | 0 | my $name; | ||||
235 | 0 | 0 | 0 | if ($fullpath =~ /.+::(.+)$/) { | |||
236 | 0 | 0 | $name = $1; | ||||
237 | } | ||||||
238 | |||||||
239 | 0 | 0 | 0 | if ($name) { | |||
240 | 0 | 0 | 0 | print "[debug] Rendering template: $name\n" if $ENV{KELPX_SWEET_DEBUG}; | |||
241 | 0 | 0 | $self->template($name, $self->stash); | ||||
242 | } | ||||||
243 | 1 | 6 | }; | ||||
244 | |||||||
245 | # if 'has' is not available (ie: no Moose, Moo, Mouse, etc), then import our own small version | ||||||
246 | 1 | 50 | 10 | unless ($caller->can('has')) { | |||
247 | 1 | 1 | *{"${caller}::has"} = \&_has; | ||||
1 | 2 | ||||||
248 | } | ||||||
249 | |||||||
250 | # if 'around' is not available, import a small version of our own | ||||||
251 | { | ||||||
252 | 1 | 1 | 6 | no warnings 'redefine'; | |||
1 | 2 | ||||||
1 | 254 | ||||||
1 | 1 | ||||||
253 | 1 | 50 | 5 | unless ($caller->can('around')) { | |||
254 | 1 | 15 | *{"${caller}::around"} = sub { | ||||
255 | 0 | 0 | my ($method, $code) = @_; | ||||
256 | |||||||
257 | 0 | my $fullpkg = "${caller}::${method}"; | |||||
258 | 0 | my $old_code = \&$fullpkg; | |||||
259 | 0 | *{"${fullpkg}"} = sub { | |||||
260 | 0 | 0 | $code->($old_code, @_); | ||||
261 | 0 | }; | |||||
262 | 1 | 3 | }; | ||||
263 | } | ||||||
264 | } | ||||||
265 | } | ||||||
266 | } | ||||||
267 | |||||||
268 | sub _has { | ||||||
269 | 0 | 0 | my ($acc, %attrs) = @_; | ||||
270 | 0 | my $class = caller; | |||||
271 | 0 | my $ro = 0; | |||||
272 | 0 | my $rq = 0; | |||||
273 | 0 | my $df; | |||||
274 | 0 | 0 | if (%attrs) { | ||||
275 | 0 | 0 | if ($attrs{is} eq 'ro') { $ro = 1; } | ||||
0 | |||||||
276 | 0 | 0 | if ($attrs{required}) { $rq = 1; } | ||||
0 | |||||||
277 | 0 | 0 | if ($attrs{default}) { $df = $attrs{default}; } | ||||
0 | |||||||
278 | |||||||
279 | 0 | 0 | if ($df) { | ||||
280 | 0 | 0 | die "has: default expects a code reference\n" | ||||
281 | unless ref $df eq 'CODE'; | ||||||
282 | } | ||||||
283 | } | ||||||
284 | |||||||
285 | { | ||||||
286 | 1 | 1 | 6 | no strict 'refs'; | |||
1 | 1 | ||||||
1 | 226 | ||||||
0 | |||||||
287 | 0 | *{"${class}::${acc}"} = sub { | |||||
288 | #if ($attrs{default}) { $_[0]->{$name} = $attrs{default}; } | ||||||
289 | 0 | 0 | 0 | 0 | if ($rq and not $df) { | ||
290 | 0 | 0 | 0 | if (not $_[0]->{$acc} and not $_[1]) { | |||
291 | 0 | die "You attempted to use a field that can't be left blank: ${acc}"; | |||||
292 | } | ||||||
293 | } | ||||||
294 | |||||||
295 | 0 | 0 | if ($df) { $_[0]->{$acc} = $df->(); } | ||||
0 | |||||||
296 | |||||||
297 | 0 | 0 | if (@_ == 2) { | ||||
298 | 0 | 0 | die "Can't modify a readonly accessor: ${acc}" | ||||
299 | if $ro; | ||||||
300 | 0 | $_[0]->{$acc} = $_[1]; | |||||
301 | } | ||||||
302 | 0 | return $_[0]->{$acc}; | |||||
303 | 0 | }; | |||||
304 | } | ||||||
305 | } | ||||||
306 | |||||||
307 | sub new { | ||||||
308 | 0 | 0 | 0 | bless { @_[ 1 .. $#_ ] }, $_[0]; | |||
309 | } | ||||||
310 | |||||||
311 | =head1 NAME | ||||||
312 | |||||||
313 | Pulp - Give your Kelp applications more juice | ||||||
314 | |||||||
315 | =head1 DESCRIPTION | ||||||
316 | |||||||
317 | 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? Pulp attempts to do just that. | ||||||
318 | |||||||
319 | =head1 SIMPLE TUTORIAL | ||||||
320 | |||||||
321 | For the most part, your original C |
||||||
322 | |||||||
323 | B |
||||||
324 | |||||||
325 | package MyApp; | ||||||
326 | use Pulp; | ||||||
327 | |||||||
328 | maps ['Main']; | ||||||
329 | |||||||
330 | Yep, that's the complete code for your base. You pass C |
||||||
331 | It will look for them in C |
||||||
332 | Next, let's create that file | ||||||
333 | |||||||
334 | B |
||||||
335 | |||||||
336 | package MyApp::Route::Main; | ||||||
337 | |||||||
338 | use Pulp::Route; | ||||||
339 | |||||||
340 | get '/' => 'Controller::Root::hello'; | ||||||
341 | get '/nocontroller' => sub { 'Hello, world from no controller!' }; | ||||||
342 | |||||||
343 | Simply use C |
||||||
344 | but that makes the whole idea of this module pointless ;) | ||||||
345 | It will load C |
||||||
346 | any of your arguments will also be sent the method inside that controller, so you don't need to do anything else! | ||||||
347 | |||||||
348 | Finally, we can create the controller | ||||||
349 | |||||||
350 | B |
||||||
351 | |||||||
352 | package MyApp::Controller::Root; | ||||||
353 | |||||||
354 | use Pulp::Controller; | ||||||
355 | |||||||
356 | sub hello { | ||||||
357 | my ($self) = @_; | ||||||
358 | return "Hello, world!"; | ||||||
359 | } | ||||||
360 | |||||||
361 | You now have a fully functional Kelp app! Remember, because this module is just a wrapper, you can do pretty much anything L |
||||||
362 | can, like C<$self->>param> for example. | ||||||
363 | |||||||
364 | =head1 SUGARY SYNTAX | ||||||
365 | |||||||
366 | 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 | ||||||
367 | routes. It uses a similar syntax to L |
||||||
368 | |||||||
369 | =head2 get | ||||||
370 | |||||||
371 | This will trigger a standard GET request. | ||||||
372 | |||||||
373 | get '/mypage' => sub { 'It works' }; | ||||||
374 | |||||||
375 | =head2 post | ||||||
376 | |||||||
377 | Will trigger on POST requests only | ||||||
378 | |||||||
379 | post '/someform' => sub { 'Posted like a boss' }; | ||||||
380 | |||||||
381 | =head2 any | ||||||
382 | |||||||
383 | Will trigger on POST B |
||||||
384 | |||||||
385 | any '/omni' => sub { 'Hit me up on any request' }; | ||||||
386 | |||||||
387 | =head2 bridge | ||||||
388 | |||||||
389 | Bridges are cool, so please check out the Kelp documentation for more information on what they do and how they work. | ||||||
390 | |||||||
391 | bridge '/users/:id' => sub { | ||||||
392 | unless ($self->user->logged_in) { | ||||||
393 | return; | ||||||
394 | } | ||||||
395 | |||||||
396 | return 1; | ||||||
397 | }; | ||||||
398 | |||||||
399 | get '/users/:id/view' => 'Controller::Users::view'; | ||||||
400 | |||||||
401 | =head2 has | ||||||
402 | |||||||
403 | If you only want basic accessors and Pulp detects you don't have any OOP frameworks activated with C |
||||||
404 | own little method which works similar to L |
||||||
405 | |||||||
406 | package MyApp; | ||||||
407 | |||||||
408 | use Pulp; | ||||||
409 | has 'x' => ( is => 'rw', default => sub { "Hello, world" } ); | ||||||
410 | |||||||
411 | package MyApp::Controller::Main; | ||||||
412 | |||||||
413 | use Pulp::Controller; | ||||||
414 | |||||||
415 | sub hello { shift->x; } # Hello, world | ||||||
416 | |||||||
417 | =head2 around | ||||||
418 | |||||||
419 | Need more power? Want to modify the default C |
||||||
420 | This allows you to tap into build if you really want to for some reason. | ||||||
421 | |||||||
422 | package MyApp; | ||||||
423 | |||||||
424 | use Pulp; | ||||||
425 | |||||||
426 | around 'build' => sub { | ||||||
427 | my $method = shift; | ||||||
428 | my $self = shift; | ||||||
429 | my $routes = $self->routes; | ||||||
430 | $routes->add('/manual' => sub { "Manually added" }); | ||||||
431 | |||||||
432 | $self->$method(@_); | ||||||
433 | }; | ||||||
434 | |||||||
435 | =head1 MODELS | ||||||
436 | |||||||
437 | You can always use an attribute to create a database connection, or separate them using models in a slightly cleaner way. | ||||||
438 | 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 | ||||||
439 | arguments it might have (like the dbi line, username and password). | ||||||
440 | |||||||
441 | # config.pl | ||||||
442 | models => { | ||||||
443 | 'LittleDB' => { | ||||||
444 | 'model' => 'TestApp::Model::LittleDB', | ||||||
445 | 'args' => ['dbi:SQLite:testapp.db'], | ||||||
446 | }, | ||||||
447 | }, | ||||||
448 | |||||||
449 | Then, you create C |
||||||
450 | |||||||
451 | package TestApp::Model::LittleDB; | ||||||
452 | |||||||
453 | use Pulp::Model; | ||||||
454 | use DBIx::Lite; | ||||||
455 | |||||||
456 | sub build { | ||||||
457 | my ($self, @args) = @_; | ||||||
458 | return DBIx::Lite->connect(@args); | ||||||
459 | } | ||||||
460 | |||||||
461 | As you can see, the C |
||||||
462 | |||||||
463 | That's all you need. Now you can pull that model instance out at any time in your controllers with C |
||||||
464 | |||||||
465 | package TestApp::Controller::User; | ||||||
466 | |||||||
467 | use Pulp::Controller; | ||||||
468 | |||||||
469 | sub users { | ||||||
470 | my ($self) = @_; | ||||||
471 | my @users = $self->model('LittleDB')->table('users')->all; | ||||||
472 | return join ', ', map { $_->name } @users; | ||||||
473 | } | ||||||
474 | |||||||
475 | =head2 Named ResultSets | ||||||
476 | |||||||
477 | 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 | ||||||
478 | from the C |
||||||
479 | |||||||
480 | package TestApp::Model::LittleDB; | ||||||
481 | |||||||
482 | use Pulp::Model; | ||||||
483 | use DBIx::Lite; | ||||||
484 | |||||||
485 | sub build { | ||||||
486 | my ($self, @args) = @_; | ||||||
487 | my $schema = DBIx::Lite->connect(@args); | ||||||
488 | return { | ||||||
489 | 'User' => $schema->table('users'), | ||||||
490 | 'Product' => $schema->table('products'), | ||||||
491 | }; | ||||||
492 | } | ||||||
493 | |||||||
494 | Then, you can do this stuff in your controllers | ||||||
495 | |||||||
496 | package TestApp::Controller::Assets; | ||||||
497 | |||||||
498 | sub users { | ||||||
499 | my ($self) = @_; | ||||||
500 | my @users = $self->model('LittleDB::User')->all; | ||||||
501 | return join " ", map { $_->name . " (" . $_->email . ")" } @users; |
||||||
502 | } | ||||||
503 | |||||||
504 | sub products { | ||||||
505 | my ($self) = @_; | ||||||
506 | my @products = $self->model('LittleDB::Product')->all; | ||||||
507 | return join " ", map { $_->name . " (" . sprintf("%.2f", $_->value) . ")" } @products; |
||||||
508 | } | ||||||
509 | |||||||
510 | |||||||
511 | =head2 Models and DBIx::Class | ||||||
512 | |||||||
513 | If you enjoy the way Catalyst handles DBIx::Class models, you're going to love this (I hope so, at least). Pulp will automagically | ||||||
514 | create models based on the sources of your schema if it detects it's a DBIx::Class::Schema. | ||||||
515 | Nothing really has to change, Pulp will figure it out on its own. | ||||||
516 | |||||||
517 | package TestApp::Model::LittleDB; | ||||||
518 | |||||||
519 | use Pulp::Model; | ||||||
520 | use LittleDB::Schema; | ||||||
521 | |||||||
522 | sub build { | ||||||
523 | my ($self, @args) = @_; | ||||||
524 | return LittleDB::Schema->connect(@args); | ||||||
525 | } | ||||||
526 | |||||||
527 | Then just use it as you normally would in Catalyst (except we store it in C<$self>, not C<$c>). | ||||||
528 | |||||||
529 | package TestApp::Controller::User; | ||||||
530 | |||||||
531 | use Pulp::Controller; | ||||||
532 | |||||||
533 | sub users { | ||||||
534 | my ($self) = @_; | ||||||
535 | my @users = $self->model('LittleDB::User')->all; | ||||||
536 | return join ', ', map { $_->name } @users; | ||||||
537 | } | ||||||
538 | |||||||
539 | Pulp will loop through all your schemas sources and create models based on your alias, and the sources name. So, C |
||||||
540 | |||||||
541 | 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! | ||||||
542 | |||||||
543 | .----------------------------------------------------------. | ||||||
544 | | Model | Alias | | ||||||
545 | +--------------------------------------+-------------------+ | ||||||
546 | | TestApp::Model::LittleDB | LittleDB | | ||||||
547 | | LittleDB::Schema::ResultSet::User | LittleDB::User | | ||||||
548 | | LittleDB::Schema::ResultSet::Product | LittleDB::Product | | ||||||
549 | '--------------------------------------+-------------------' | ||||||
550 | |||||||
551 | =head2 Automated API generation | ||||||
552 | |||||||
553 | Did you know Pulp can automatically create an API for your DBIx::Class schema? Currently this feature is still in beta, and only works with | ||||||
554 | searching. Simply pass C<-api> as an import option like so. | ||||||
555 | |||||||
556 | package TestApp::Model::LittleDB; | ||||||
557 | |||||||
558 | use Pulp::Model -api => 1; | ||||||
559 | ... | ||||||
560 | |||||||
561 | This will tell Pulp to do all the work for you, and generates a basic JSON API. | ||||||
562 | Some of the commands are below: | ||||||
563 | |||||||
564 | =head3 list | ||||||
565 | |||||||
566 | Lists all rows found for a particular resultset | ||||||
567 | |||||||
568 | # curl http://localhost:5000/api/user/list | ||||||
569 | [ | ||||||
570 | { | ||||||
571 | "email" : "admin@company.ltd", | ||||||
572 | "name" : "Admin User", | ||||||
573 | "id" : 1 | ||||||
574 | }, | ||||||
575 | { | ||||||
576 | "email" : "user@company.ltd", | ||||||
577 | "name" : "Normal User", | ||||||
578 | "id" : 2 | ||||||
579 | } | ||||||
580 | ] | ||||||
581 | |||||||
582 | =head3 find | ||||||
583 | |||||||
584 | Obtain a single row based on an id. | ||||||
585 | |||||||
586 | # curl http://localhost:5000/api/user/find/2 | ||||||
587 | { | ||||||
588 | "email" : "user@company.ltd", | ||||||
589 | "name" : "Normal User", | ||||||
590 | "id" : 2 | ||||||
591 | } | ||||||
592 | |||||||
593 | =head3 search | ||||||
594 | |||||||
595 | You can also perform a search, passing query parameters as your search arguments. If no parameters are passed, you'll | ||||||
596 | get all results back. | ||||||
597 | |||||||
598 | # curl http://localhost:5000/api/user/search?email=admin@company.ltd&id=1 | ||||||
599 | [ | ||||||
600 | { | ||||||
601 | "email" : "admin@company.ltd", | ||||||
602 | "name" : "Admin User", | ||||||
603 | "id" : 1 | ||||||
604 | } | ||||||
605 | ] | ||||||
606 | |||||||
607 | =head1 VIEWS | ||||||
608 | |||||||
609 | OK, so to try and not separate too much, I've chosen not to include views. Just use the standard Kelp modules | ||||||
610 | (ie: L |
||||||
611 | |||||||
612 | =head2 detach | ||||||
613 | |||||||
614 | This method will call C for you with the added benefit of automatically filling out the filename and including whatever | ||||||
615 | is in the stash for you. | ||||||
616 | |||||||
617 | package MyApp::Controller::Awesome; | ||||||
618 | |||||||
619 | use Pulp::Controller; | ||||||
620 | |||||||
621 | sub hello { | ||||||
622 | my ($self) = @_; | ||||||
623 | $self->stash->{name} = 'World'; | ||||||
624 | $self->detach; | ||||||
625 | } | ||||||
626 | |||||||
627 | Then, you just create C |
||||||
628 | |||||||
629 | Hello, [% name %] |
||||||
630 | |||||||
631 | While not really required, it does save a bit of typing and can come in quite useful. | ||||||
632 | |||||||
633 | =head1 IMPORT OPTIONS | ||||||
634 | |||||||
635 | =head2 -auto | ||||||
636 | |||||||
637 | Importing -auto will automatically include any route modules within your C |
||||||
638 | For example, we have two controllers, C |
||||||
639 | |||||||
640 | package MyApp::Route::Main; | ||||||
641 | |||||||
642 | use Pulp::Route; | ||||||
643 | |||||||
644 | get '/' => sub { "Hi" }; | ||||||
645 | |||||||
646 | package MyApp::Route::New; | ||||||
647 | |||||||
648 | use Pulp::Route; | ||||||
649 | |||||||
650 | get '/new/url' => sub { "New one" }; | ||||||
651 | |||||||
652 | Then to kick off our app, all we need is | ||||||
653 | |||||||
654 | package MyApp; | ||||||
655 | use Pulp -auto => 1; | ||||||
656 | |||||||
657 | That's it. Pulp will complain if you attempt to use C |
||||||
658 | |||||||
659 | =head1 REALLY COOL THINGS TO NOTE | ||||||
660 | |||||||
661 | =head2 Default imports | ||||||
662 | |||||||
663 | You should be aware that Pulp will import warnings, strict and true for you. Because of this, there is no requirement to | ||||||
664 | add a true value to the end of your file. I chose this because it just makes things look a little cleaner. | ||||||
665 | |||||||
666 | =head2 Pulp starter | ||||||
667 | |||||||
668 | On installation of Pulp, you'll receive a file called C |
||||||
669 | and it will create a working test app with minimal boilerplate so you can get started straight away. Just run it as: | ||||||
670 | |||||||
671 | $ pulp MyApp | ||||||
672 | $ pulp Something::With::A::Larger::Namespace | ||||||
673 | |||||||
674 | =head1 SEE ALSO | ||||||
675 | |||||||
676 | L |
||||||
677 | |||||||
678 | |||||||
679 | =head1 AUTHOR | ||||||
680 | |||||||
681 | Brad Haywood |
||||||
682 | |||||||
683 | =head1 LICENSE | ||||||
684 | |||||||
685 | You may distribute this code under the same terms as Perl itself. | ||||||
686 | |||||||
687 | =cut | ||||||
688 | |||||||
689 | 1; | ||||||
690 | __END__ |