Newer Older
598 lines | 14.167kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojolicious::Routes::Route;
2
use Mojo::Base -base;
3

            
4
use Carp 'croak';
5
use Mojolicious::Routes::Pattern;
6
use Scalar::Util qw(blessed weaken);
7

            
8
has [qw(inline parent partial)];
9
has 'children' => sub { [] };
10
has pattern    => sub { Mojolicious::Routes::Pattern->new };
11

            
12
sub AUTOLOAD {
13
  my $self = shift;
14

            
15
  my ($package, $method) = our $AUTOLOAD =~ /^([\w:]+)::(\w+)$/;
16
  croak "Undefined subroutine &${package}::$method called"
17
    unless blessed $self && $self->isa(__PACKAGE__);
18

            
19
  # Call shortcut with current route
20
  croak qq{Can't locate object method "$method" via package "$package"}
21
    unless my $shortcut = $self->root->shortcuts->{$method};
22
  return $self->$shortcut(@_);
23
}
24

            
25
sub DESTROY { }
26

            
27
sub new { shift->SUPER::new->parse(@_) }
28

            
29
sub add_child {
30
  my ($self, $route) = @_;
31
  weaken $route->remove->parent($self)->{parent};
32
  push @{$self->children}, $route;
33
  return $self;
34
}
35

            
36
sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
37

            
38
sub bridge { shift->route(@_)->inline(1) }
39

            
40
sub delete { shift->_generate_route(DELETE => @_) }
41

            
42
sub detour { shift->partial(1)->to(@_) }
43

            
44
sub find {
45
  my ($self, $name) = @_;
46

            
47
  my @children = (@{$self->children});
48
  my $candidate;
49
  while (my $child = shift @children) {
50

            
51
    # Match
52
    $candidate = $child->has_custom_name ? return $child : $child
53
      if $child->name eq $name;
54

            
55
    # Search children too
56
    push @children, @{$child->children};
57
  }
58

            
59
  return $candidate;
60
}
61

            
62
sub get { shift->_generate_route(GET => @_) }
63

            
64
sub has_conditions {
65
  my $self = shift;
66
  return 1 if @{$self->over || []};
67
  return undef unless my $parent = $self->parent;
68
  return $parent->has_conditions;
69
}
70

            
71
sub has_custom_name { !!shift->{custom} }
72

            
73
sub has_websocket {
74
  my $self = shift;
75
  return 1 if $self->is_websocket;
76
  return undef unless my $parent = $self->parent;
77
  return $parent->is_websocket;
78
}
79

            
80
sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
81

            
82
sub is_websocket { !!shift->{websocket} }
83

            
84
sub name {
85
  my $self = shift;
86
  return $self->{name} unless @_;
87
  $self->{name}   = shift;
88
  $self->{custom} = 1;
89
  return $self;
90
}
91

            
92
sub options { shift->_generate_route(OPTIONS => @_) }
93

            
94
sub over {
95
  my $self = shift;
96

            
97
  # Routes with conditions can't be cached
98
  return $self->{over} unless @_;
99
  my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
100
  return $self unless @$conditions;
101
  $self->{over} = $conditions;
102
  $self->root->cache(0);
103

            
104
  return $self;
105
}
106

            
107
sub parse {
108
  my $self = shift;
109
  $self->{name} = $self->pattern->parse(@_)->pattern // '';
110
  $self->{name} =~ s/\W+//g;
111
  return $self;
112
}
113

            
114
sub patch { shift->_generate_route(PATCH => @_) }
115
sub post  { shift->_generate_route(POST  => @_) }
116
sub put   { shift->_generate_route(PUT   => @_) }
117

            
118
sub remove {
119
  my $self = shift;
120
  return $self unless my $parent = $self->parent;
121
  @{$parent->children} = grep { $_ ne $self } @{$parent->children};
122
  return $self->parent(undef);
123
}
124

            
125
sub render {
126
  my ($self, $path, $values) = @_;
127

            
128
  # Render pattern
129
  my $prefix = $self->pattern->render($values, !$path);
130
  $path = "$prefix$path" unless $prefix eq '/';
131
  $path ||= '/' unless my $parent = $self->parent;
132

            
133
  # Let parent render
134
  return $parent ? $parent->render($path, $values) : $path;
135
}
136

            
137
sub root {
138
  my $root = my $parent = shift;
139
  $root = $parent while $parent = $parent->parent;
140
  return $root;
141
}
142

            
143
sub route {
144
  my $self   = shift;
145
  my $route  = $self->add_child($self->new(@_))->children->[-1];
146
  my $format = $self->pattern->constraints->{format};
147
  $route->pattern->constraints->{format} //= 0 if defined $format && !$format;
148
  return $route;
149
}
150

            
151
sub to {
152
  my $self = shift;
153

            
154
  my $pattern = $self->pattern;
155
  return $pattern->defaults unless @_;
156
  my ($shortcut, %defaults) = _defaults(@_);
157

            
158
  if ($shortcut) {
159

            
160
    # Application
161
    if (ref $shortcut || $shortcut =~ /^[\w:]+$/) {
162
      $defaults{app} = $shortcut;
163
    }
164

            
165
    # Controller and action
166
    elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
167
      $defaults{controller} = $1 if defined $1;
168
      $defaults{action}     = $2 if defined $2;
169
    }
170
  }
171

            
172
  $pattern->defaults({%{$pattern->defaults}, %defaults});
173

            
174
  return $self;
175
}
176

            
177
sub to_string {
178
  my $self = shift;
179
  my $pattern = $self->parent ? $self->parent->to_string : '';
180
  $pattern .= $self->pattern->pattern if $self->pattern->pattern;
181
  return $pattern;
182
}
183

            
184
sub under { shift->_generate_route(under => @_) }
185

            
186
sub via {
187
  my $self = shift;
188
  return $self->{via} unless @_;
189
  my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
190
  $self->{via} = $methods if @$methods;
191
  return $self;
192
}
193

            
194
sub websocket {
195
  my $route = shift->get(@_);
196
  $route->{websocket} = 1;
197
  return $route;
198
}
199

            
200
sub _defaults {
201

            
202
  # Hash or shortcut (one)
203
  return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1;
204

            
205
  # Shortcut and values (odd)
206
  return shift, @_ if @_ % 2;
207

            
208
  # Shortcut and hash or just values (even)
209
  return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_);
210
}
211

            
212
sub _generate_route {
213
  my ($self, $methods, @args) = @_;
214

            
215
  my ($cb, @conditions, @constraints, %defaults, $name, $pattern);
216
  while (defined(my $arg = shift @args)) {
217

            
218
    # First scalar is the pattern
219
    if (!ref $arg && !$pattern) { $pattern = $arg }
220

            
221
    # Scalar
222
    elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
223

            
224
    # Last scalar is the route name
225
    elsif (!ref $arg) { $name = $arg }
226

            
227
    # Callback
228
    elsif (ref $arg eq 'CODE') { $cb = $arg }
229

            
230
    # Constraints
231
    elsif (ref $arg eq 'ARRAY') { @constraints = @$arg }
232

            
233
    # Defaults
234
    elsif (ref $arg eq 'HASH') { %defaults = %$arg }
235
  }
236

            
237
  # Callback
238
  $defaults{cb} = $cb if $cb;
239

            
240
  # Create bridge or route
241
  my $route
242
    = $methods eq 'under'
243
    ? $self->bridge($pattern, @constraints)
244
    : $self->route($pattern, @constraints)->via($methods);
245
  $route->over(\@conditions)->to(\%defaults);
246

            
247
  return defined $name ? $route->name($name) : $route;
248
}
249

            
250
1;
251

            
252
=encoding utf8
253

            
254
=head1 NAME
255

            
256
Mojolicious::Routes::Route - Route
257

            
258
=head1 SYNOPSIS
259

            
260
  use Mojolicious::Routes::Route;
261

            
262
  my $r = Mojolicious::Routes::Route->new;
263

            
264
=head1 DESCRIPTION
265

            
266
L<Mojolicious::Routes::Route> is the route container used by
267
L<Mojolicious::Routes>.
268

            
269
=head1 ATTRIBUTES
270

            
271
L<Mojolicious::Routes::Route> implements the following attributes.
272

            
273
=head2 children
274

            
275
  my $children = $r->children;
276
  $r           = $r->children([Mojolicious::Routes::Route->new]);
277

            
278
The children of this route, used for nesting routes.
279

            
280
=head2 inline
281

            
282
  my $bool = $r->inline;
283
  $r       = $r->inline($bool);
284

            
285
Allow L</"bridge"> semantics for this route.
286

            
287
=head2 parent
288

            
289
  my $parent = $r->parent;
290
  $r         = $r->parent(Mojolicious::Routes::Route->new);
291

            
292
The parent of this route, used for nesting routes.
293

            
294
=head2 partial
295

            
296
  my $bool = $r->partial;
297
  $r       = $r->partial($bool);
298

            
299
Route has no specific end, remaining characters will be captured in C<path>.
300

            
301
=head2 pattern
302

            
303
  my $pattern = $r->pattern;
304
  $r          = $r->pattern(Mojolicious::Routes::Pattern->new);
305

            
306
Pattern for this route, defaults to a L<Mojolicious::Routes::Pattern> object.
307

            
308
=head1 METHODS
309

            
310
L<Mojolicious::Routes::Route> inherits all methods from L<Mojo::Base> and
311
implements the following new ones.
312

            
313
=head2 new
314

            
315
  my $r = Mojolicious::Routes::Route->new;
316
  my $r = Mojolicious::Routes::Route->new('/:controller/:action');
317

            
318
Construct a new L<Mojolicious::Routes::Route> object and <parse> pattern if
319
necessary.
320

            
321
=head2 add_child
322

            
323
  $r = $r->add_child(Mojolicious::Routes::Route->new);
324

            
325
Add a new child to this route, it will be automatically removed from its
326
current parent if necessary.
327

            
328
  # Reattach route
329
  $r->add_child($r->find('foo'));
330

            
331
=head2 any
332

            
333
  my $route = $r->any('/:foo' => sub {...});
334
  my $route = $r->any([qw(GET POST)] => '/:foo' => sub {...});
335

            
336
Generate route matching any of the listed HTTP request methods or all. See
337
also the L<Mojolicious::Lite> tutorial for more argument variations.
338

            
339
  $r->any('/user')->to('user#whatever');
340

            
341
=head2 bridge
342

            
343
  my $bridge = $r->bridge;
344
  my $bridge = $r->bridge('/:action');
345
  my $bridge = $r->bridge('/:action', action => qr/\w+/);
346
  my $bridge = $r->bridge(format => 0);
347

            
348
Generate bridge route with optional pattern and restrictive placeholders.
349

            
350
  my $auth = $r->bridge('/user')->to('user#auth');
351
  $auth->get('/show')->to('#show');
352
  $auth->post('/create')->to('#create');
353

            
354
=head2 delete
355

            
356
  my $route = $r->delete('/:foo' => sub {...});
357

            
358
Generate route matching only DELETE requests. See also the
359
L<Mojolicious::Lite> tutorial for more argument variations.
360

            
361
  $r->delete('/user')->to('user#remove');
362

            
363
=head2 detour
364

            
365
  $r = $r->detour(action => 'foo');
366
  $r = $r->detour('controller#action');
367
  $r = $r->detour(Mojolicious->new, foo => 'bar');
368
  $r = $r->detour('MyApp', {foo => 'bar'});
369

            
370
Set default parameters for this route and allow partial matching to simplify
371
application embedding, takes the same arguments as L</"to">.
372

            
373
=head2 find
374

            
375
  my $route = $r->find('foo');
376

            
377
Find child route by name, custom names have precedence over automatically
378
generated ones.
379

            
380
  $r->find('show_user')->to(foo => 'bar');
381

            
382
=head2 get
383

            
384
  my $route = $r->get('/:foo' => sub {...});
385

            
386
Generate route matching only GET requests. See also the L<Mojolicious::Lite>
387
tutorial for more argument variations.
388

            
389
  $r->get('/user')->to('user#show');
390

            
391
=head2 has_conditions
392

            
393
  my $bool = $r->has_conditions;
394

            
395
Check if this route has active conditions.
396

            
397
=head2 has_custom_name
398

            
399
  my $bool = $r->has_custom_name;
400

            
401
Check if this route has a custom name.
402

            
403
=head2 has_websocket
404

            
405
  my $bool = $r->has_websocket;
406

            
407
Check if this route has a WebSocket ancestor.
408

            
409
=head2 is_endpoint
410

            
411
  my $bool = $r->is_endpoint;
412

            
413
Check if this route qualifies as an endpoint.
414

            
415
=head2 is_websocket
416

            
417
  my $bool = $r->is_websocket;
418

            
419
Check if this route is a WebSocket.
420

            
421
=head2 name
422

            
423
  my $name = $r->name;
424
  $r       = $r->name('foo');
425

            
426
The name of this route, defaults to an automatically generated name based on
427
the route pattern. Note that the name C<current> is reserved for referring to
428
the current route.
429

            
430
  $r->get('/user')->to('user#show')->name('show_user');
431

            
432
=head2 options
433

            
434
  my $route = $r->options('/:foo' => sub {...});
435

            
436
Generate route matching only OPTIONS requests. See also the
437
L<Mojolicious::Lite> tutorial for more argument variations.
438

            
439
  $r->options('/user')->to('user#overview');
440

            
441
=head2 over
442

            
443
  my $over = $r->over;
444
  $r       = $r->over(foo => 1);
445
  $r       = $r->over(foo => 1, bar => {baz => 'yada'});
446
  $r       = $r->over([foo => 1, bar => {baz => 'yada'}]);
447

            
448
Activate conditions for this route. Note that this automatically disables the
449
routing cache, since conditions are too complex for caching.
450

            
451
  $r->get('/foo')->over(host => qr/mojolicio\.us/)->to('foo#bar');
452

            
453
=head2 parse
454

            
455
  $r = $r->parse('/:action');
456
  $r = $r->parse('/:action', action => qr/\w+/);
457
  $r = $r->parse(format => 0);
458

            
459
Parse pattern.
460

            
461
=head2 patch
462

            
463
  my $route = $r->patch('/:foo' => sub {...});
464

            
465
Generate route matching only PATCH requests. See also the L<Mojolicious::Lite>
466
tutorial for more argument variations.
467

            
468
  $r->patch('/user')->to('user#update');
469

            
470
=head2 post
471

            
472
  my $route = $r->post('/:foo' => sub {...});
473

            
474
Generate route matching only POST requests. See also the L<Mojolicious::Lite>
475
tutorial for more argument variations.
476

            
477
  $r->post('/user')->to('user#create');
478

            
479
=head2 put
480

            
481
  my $route = $r->put('/:foo' => sub {...});
482

            
483
Generate route matching only PUT requests. See also the L<Mojolicious::Lite>
484
tutorial for more argument variations.
485

            
486
  $r->put('/user')->to('user#replace');
487

            
488
=head2 remove
489

            
490
  $r = $r->remove;
491

            
492
Remove route from parent.
493

            
494
  # Remove route completely
495
  $r->find('foo')->remove;
496

            
497
  # Reattach route to new parent
498
  $r->route('/foo')->add_child($r->find('bar')->remove);
499

            
500
=head2 render
501

            
502
  my $path = $r->render($suffix);
503
  my $path = $r->render($suffix, {foo => 'bar'});
504

            
505
Render route with parameters into a path.
506

            
507
=head2 root
508

            
509
  my $root = $r->root;
510

            
511
The L<Mojolicious::Routes> object this route is an descendent of.
512

            
513
  $r->root->cache(0);
514

            
515
=head2 route
516

            
517
  my $route = $r->route;
518
  my $route = $r->route('/:action');
519
  my $route = $r->route('/:action', action => qr/\w+/);
520
  my $route = $r->route(format => 0);
521

            
522
Generate route matching all HTTP request methods with optional pattern and
523
restrictive placeholders.
524

            
525
=head2 to
526

            
527
  my $defaults = $r->to;
528
  $r           = $r->to(action => 'foo');
529
  $r           = $r->to({action => 'foo'});
530
  $r           = $r->to('controller#action');
531
  $r           = $r->to('controller#action', foo => 'bar');
532
  $r           = $r->to('controller#action', {foo => 'bar'});
533
  $r           = $r->to(Mojolicious->new);
534
  $r           = $r->to(Mojolicious->new, foo => 'bar');
535
  $r           = $r->to(Mojolicious->new, {foo => 'bar'});
536
  $r           = $r->to('MyApp');
537
  $r           = $r->to('MyApp', foo => 'bar');
538
  $r           = $r->to('MyApp', {foo => 'bar'});
539

            
540
Set default parameters for this route.
541

            
542
=head2 to_string
543

            
544
  my $str = $r->to_string;
545

            
546
Stringify the whole route.
547

            
548
=head2 under
549

            
550
  my $bridge = $r->under(sub {...});
551
  my $bridge = $r->under('/:foo');
552

            
553
Generate bridge route. See also the L<Mojolicious::Lite> tutorial for more
554
argument variations.
555

            
556
  my $auth = $r->under('/user')->to('user#auth');
557
  $auth->get('/show')->to('#show');
558
  $auth->post('/create')->to('#create');
559

            
560
=head2 via
561

            
562
  my $methods = $r->via;
563
  $r          = $r->via('GET');
564
  $r          = $r->via(qw(GET POST));
565
  $r          = $r->via([qw(GET POST)]);
566

            
567
Restrict HTTP methods this route is allowed to handle, defaults to no
568
restrictions.
569

            
570
  $r->route('/foo')->via(qw(GET POST))->to('foo#bar');
571

            
572
=head2 websocket
573

            
574
  my $ws = $r->websocket('/:foo' => sub {...});
575

            
576
Generate route matching only WebSocket handshakes. See also the
577
L<Mojolicious::Lite> tutorial for more argument variations.
578

            
579
  $r->websocket('/echo')->to('example#echo');
580

            
581
=head1 SHORTCUTS
582

            
583
In addition to the attributes and methods above you can also call shortcuts
584
on L<Mojolicious::Routes::Route> objects.
585

            
586
  $r->root->add_shortcut(firefox => sub {
587
    my ($r, $path) = @_;
588
    $r->get($path, agent => qr/Firefox/);
589
  });
590

            
591
  $r->firefox('/welcome')->to('firefox#welcome');
592
  $r->firefox('/bye')->to('firefox#bye);
593

            
594
=head1 SEE ALSO
595

            
596
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
597

            
598
=cut