=pod

=head1 Name

Forward::Routes

=head1 Description

L<Forward::Routes>: routes for framework builders, experimental, so API might change

=head1 Features


=head2 Basic Routes

    $r = Forward::Routes->new;
    $r->add_route('foo/bar');

    my $m = $r->match(get => 'foo/bar');
    is_deeply $m->[0]->params => {};

    my $m = $r->match(get => 'foo/hello');
    is $m, undef;


=head2 Placeholders

    $r = Forward::Routes->new;
    $r->add_route(':foo/:bar');

    $m = $r->match(get => 'hello/there');
    is_deeply $m->[0]->params => {foo => 'hello', bar => 'there'};


=head2 Format Constraints and Detection

    $r = Forward::Routes->new->format('html','xml');
    $r->add_route(':foo/:bar');

    $m = $r->match(get => 'hello/there.html');
    is_deeply $m->[0]->params => {foo => 'hello', bar => 'there', format => 'html'};

    $m = $r->match(get => 'hello/there.xml');
    is_deeply $m->[0]->params => {foo => 'hello', bar => 'there', format => 'xml'};

    $m = $r->match(get => 'hello/there.jpeg');
    is $m, undef;


=head2 Nested Routes

    $r = Forward::Routes->new;
    $nested = $r->add_route(':foo');
    $nested->add_route(':bar');

    $m = $r->match(get => 'hello/there');
    is_deeply $m->[0]->params => {foo => 'hello', bar => 'there'};


=head2 Defaults for action and controller params

    $r = Forward::Routes->new;
    $r->add_route('articles')->to('foo#bar');

    $m = $r->match(get => 'articles');
    is_deeply $m->[0]->params => {controller => 'foo', action => 'bar'};

    
=head2 Constraints

    $r = Forward::Routes->new;
    $r->add_route('articles/:id')->constraints(id => qr/\d+/);
    
    $m = $r->match(get => 'articles/abc');
    ok not defined $m;
    
    $m = $r->match(get => 'articles/123');
    is_deeply $m->[0]->params => {id => 123};


=head2 Grouping

    $r = Forward::Routes->new;
    $r->add_route('world/(:country)-(:cities)')->name('hello');
    
    $m = $r->match(get => 'world/us-new_york');
    is_deeply $m->[0]->params => {country => 'us', cities => 'new_york'};


=head2 Path Building

    # build path
    is $r->build_path('hello', country => 'us', cities => 'new_york')->{path},
      'world/us-new_york';


=head2 Optional Placeholders    

    $r = Forward::Routes->new;
    $r->add_route(':year(/:month/:day)?')->name('foo');
    
    $m = $r->match(get => '2009');
    is_deeply $m->[0]->params => {year => 2009};
    
    $m = $r->match(get => '2009/12');
    ok !defined $m;
    
    $m = $r->match(get => '2009/12/10');
    is_deeply $m->[0]->params => {year => 2009, month => 12, day => 10};



    $r = Forward::Routes->new;
    $r->add_route('/hello/world(-:city)?')->name('foo');
    
    $m = $r->match(get => 'hello/world');
    is_deeply $m->[0]->params => {};
    
    $m = $r->match(get => 'hello/world-paris');
    is_deeply $m->[0]->params => {city => 'paris'};   


=head2 Optional Placeholders and Defaults

    $r = Forward::Routes->new;
    $r->add_route(':year(/:month)?/:day')->defaults(month => 1);
    
    $m = $r->match(get => '2009');
    ok not defined $m;
    
    $m = $r->match(get => '2009/12');
    is_deeply $m->[0]->params => {year => 2009, month => 1, day => 12};
    
    $m = $r->match(get => '2009/2/3');
    is_deeply $m->[0]->params => {year => 2009, month => 2, day => 3};


=head2 Method Matching

    $r = Forward::Routes->new;
    $r->add_route('logout')->via('get');
    ok $r->match(get => 'logout');
    ok !$r->match(post => 'logout');


=head2 Chaining

    $r = Forward::Routes->new;
    my $articles = $r->add_route('articles/:id')
      ->defaults(first_name => 'foo', last_name => 'bar')
      ->constraints(id => qr/\d+/)
      ->name('hot')
      ->to('hello#world')
      ->via('get','post');


=head2 Resources

    $r = Forward::Routes->new;
    $r->add_resources('users','photos','tags');
    
    $m = $r->match(get => 'photos');
    is_deeply $m->[0]->params => {controller => 'photos', action => 'index'};
    
    $m = $r->match(get => 'photos/1');
    is_deeply $m->[0]->params => {controller => 'photos', action => 'show', id => 1};
    
    $m = $r->match(put => 'photos/1');
    is_deeply $m->[0]->params => {controller => 'photos', action => 'update', id => 1};


=head2 Path Building and Resources

    $r = Forward::Routes->new;
    $r->add_resources('users','photos','tags');

    is $r->build_path('photos_update', id => 987)->{path} => 'photos/987';


=head2 Nested Resources

    $r = Forward::Routes->new;
    my $magazines = $r->add_resources('magazines');
    $magazines->add_resources('ads');

    $m = $r->match(get => 'magazines/1/ads/4');
    is_deeply $m->[0]->params =>
      {controller => 'ads', action => 'show', magazines_id => 1, ads_id => 4};




=head1 Author

ForwardEver

=head1 Copyright and License

Copyright (C) 2011, ForwardEver

This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.

=head1 Credits

Path matching and path building inspired by Viacheslav Tykhanovskyi's Router module
L<https://github.com/vti/router>

Concept of nested routes inspired by Sebastian Riedel's Mojolicious::Routes module
L<https://github.com/kraih/mojo/tree/master/lib/Mojolicious/Routes>

Concept of restful resources inspired by Ruby on Rails

=cut
