2

For example:

I know how to match www.domain.com/foo/21

sub foo : Path('/foo') Args(1) {
  my ( $self, $c, $foo_id ) = @_;
  # do stuff with foo
}

But how can I match www.domain.com/foo/21 OR www.domain.com/foo/21/bar/56 ?

sub foo : <?> {
  my ( $self, $c, $foo_id, $bar_id ) = @_;
  # do stuff with foo, and maybe do some things with bar if present
}

Thanks

Update: Following Daxim's suggestion, I tried to use :Regex

sub foo : Regex('foo/(.+?)(?:/bar/(.+))?') {
   my ( $self, $c ) = @_;
   my ( $foo_id, $bar_id ) = @{ $c->req->captures };
}

But this doesn't seem to work; the url is matched, but $bar_id is always undef. If I remove the optional opperator from the end of the regex then it does capture $bar_id correctly, but then both foo and bar must be present to get a url match. I'm not sure if this is a perl regex issue, or a Catalyst issue. Any ideas?

Update:

As Daxim points out, its a regex issue. I can't see why the above regex doesn't work, but I did manage to find one that does:

sub foo : Regex('foo/([^/]+)(?:/bar/([^/]+))?') {
   my ( $self, $c ) = @_;
   my ( $foo_id, $bar_id ) = @{ $c->req->captures };
}

(I didn't use \d+ in the captures like Daxim did as my ids might not be numeric)

Thanks all for the help and suggestions, I learnt a lot about handling urls in Catalyst :D

nick
  • 1,369
  • 2
  • 13
  • 28

2 Answers2

10

The Args attribute doesn't have to be limited to a specific number of arguments. For instance, the following should work:

sub foo :Args() {    # matches /foo, /foo/123, /foo/123/bar/456, /foo/123/bar/456/*
  my($self, $c, $foo_id, %optional_params) = @_;
  if($optional_params{bar}){
    # ...
  }
}

Keep in mind that all of the remaining URL segments after the path prefix and action name will be present in @remainder. Also, since you're not specifying how many arguments you need, Catalyst will allow a URL without any args to match this action. Validate your input accordingly!

UPDATED with :Chained example

The following (untested) catalyst actions would provide you with a little more strict action matching that you seem to be looking for. The downside is that you must rely on the stash to share data between all the actions.

sub foo :Chained('/') :PathPart :CaptureArgs(1) {
  my($self, $c, $foo_id) = @_;
  $c->stash->{foo_id} = $foo_id; # or whatever
}

sub foo_no_bar :Chained('foo') :Args(0) {
  my($self, $c) = @_;
  # matches on /foo/123 but not /foo/123/bar/456
  my $foo_id = $c->stash->{foo_id}; 
}

sub bar :Chained('foo') :PathPart :Args(1) {
  my($self, $c, $bar_id) = @_;
  my $foo_id = $c->stash->{foo_id};
  # matches on /foo/123/bar/456 but not /foo/123 or /foo/123/baz/456
}
Brian Phillips
  • 12,693
  • 3
  • 29
  • 26
  • That will also match /foo/123/baz/456 which is not what I want. – nick Dec 10 '10 at 13:49
  • I've updated with another option. Perhaps this is closer to what you're looking for. – Brian Phillips Dec 10 '10 at 14:12
  • Hmm, thats not quite what I'm after either. I don't want to handle the foo_no_bar and foo_and_bar cases as separate functions, I want only one block of code that handles both cases. I guess I could have separate functions to capture the args and then forward to a common function, but that seems overly complicated. – nick Dec 10 '10 at 14:24
  • @nick - it's hard to know what to suggest when giving advice on code you're not sharing! :-) You can, however, put your common code in the base :Chained method (`sub foo` in this example) and other arg-specific code in the separate actions. Beyond that, I think you're perhaps looking at a custom DispatchType implementation. – Brian Phillips Dec 10 '10 at 14:40
  • I haven't written any code to share yet ;) I just want a mechanism for handling an optional argument. A working version of my broken regex example would be ideal, failing that your first suggestion is fine, but has the disadvantage of having to deal with the unwanted 'baz' match scenario. Thanks for the help, much appreciated :) – nick Dec 10 '10 at 15:07
2

See item Pattern-match (:Regex and :LocalRegex) in Catalyst::Manual::Intro#Action_types.


nick writes:

I'm not sure if this is a perl regex issue, or a Catalyst issue. Any ideas?

How about simply trying it out?

repl>>> $_ = '/foo/21/bar/56'
/foo/21/bar/56

repl>>> m|foo/(\d+)(?:/bar/(\d+))?|
$VAR1 = 21;
$VAR2 = 56;

repl>>> $_ = '/foo/21'
/foo/21

repl>>> m|foo/(\d+)(?:/bar/(\d+))?|
$VAR1 = 21;
$VAR2 = undef;
daxim
  • 39,270
  • 4
  • 65
  • 132
  • Thanks for the pointer daxim, but I can't get the optional regex to work - I updated the question with the details. – nick Dec 10 '10 at 12:29
  • Yep, its a regex problem - thanks! I've updated my post with the working code. – nick Dec 10 '10 at 15:31