X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Controller-DBIC-API.git;a=blobdiff_plain;f=lib%2FCatalyst%2FController%2FDBIC%2FAPI%2FRequestArguments.pm;h=ed6713c7388957bb7a95d75b433da2ac5d4b8aa7;hp=f85a4e2f625cd91507c9ff979ea59a29b686b15b;hb=3b3479e81d9f724bce64cc965090af0a940874be;hpb=d666a194afbf36c50785acff4e7fb4e04e534374 diff --git a/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm b/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm index f85a4e2..ed6713c 100644 --- a/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm +++ b/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm @@ -6,481 +6,426 @@ use Catalyst::Controller::DBIC::API::Types(':all'); use MooseX::Types::Moose(':all'); use Scalar::Util('reftype'); use Data::Dumper; +use Catalyst::Controller::DBIC::API::Validator; use namespace::autoclean; use Catalyst::Controller::DBIC::API::JoinBuilder; - =attribute_private search_validator -A Catalyst::Controller::DBIC::API::Validator instance used solely to validate search parameters +A Catalyst::Controller::DBIC::API::Validator instance used solely to validate +search parameters. =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'search_validator', -}; - =attribute_private select_validator -A Catalyst::Controller::DBIC::API::Validator instance used solely to validate select parameters +A Catalyst::Controller::DBIC::API::Validator instance used solely to validate +select parameters. =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'select_validator', -}; - =attribute_private prefetch_validator -A Catalyst::Controller::DBIC::API::Validator instance used solely to validate prefetch parameters +A Catalyst::Controller::DBIC::API::Validator instance used solely to validate +prefetch parameters. =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'prefetch_validator', -}; +has [qw( search_validator select_validator )] => ( + is => 'ro', + isa => 'Catalyst::Controller::DBIC::API::Validator', + lazy => 1, + builder => '_build_validator', +); + +sub _build_validator { + return Catalyst::Controller::DBIC::API::Validator->new; +} parameter static => ( isa => Bool, default => 0 ); role { - my $p = shift; - - if($p->static) - { - requires qw/check_has_relation check_column_relation/; + + if ( $p->static ) { + requires + qw( check_has_relation check_column_relation prefetch_allows ); } - else - { - requires qw/_controller check_has_relation check_column_relation/; + else { + requires qw( _controller check_has_relation check_column_relation ); } -=attribute_public count is: ro, isa: Int +=attribute_public count -count is the number of rows to be returned during paging +The number of rows to be returned during paging. =cut - has 'count' => - ( - is => 'ro', - writer => '_set_count', - isa => Int, + has 'count' => ( + is => 'ro', + writer => '_set_count', + isa => Int, predicate => 'has_count', ); -=attribute_public page is: ro, isa: Int +=attribute_public page -page is what page to return while paging +What page to return while paging. =cut - has 'page' => - ( - is => 'ro', - writer => '_set_page', - isa => Int, + has 'page' => ( + is => 'ro', + writer => '_set_page', + isa => Int, predicate => 'has_page', ); -=attribute_public ordered_by is: ro, isa: L +=attribute_public offset -ordered_by is passed to ->search to determine sorting +Specifies where to start the paged result (think SQL LIMIT). =cut - has 'ordered_by' => - ( - is => 'ro', - writer => '_set_ordered_by', - isa => OrderedBy, - predicate => 'has_ordered_by', - coerce => 1, - default => sub { $p->static ? [] : undef }, + has 'offset' => ( + is => 'ro', + writer => '_set_offset', + isa => Int, + predicate => 'has_offset', ); -=attribute_public groupd_by is: ro, isa: L +=attribute_public ordered_by -grouped_by is passed to ->search to determine aggregate results +Is passed to ->search to determine sorting. =cut - has 'grouped_by' => - ( - is => 'ro', - writer => '_set_grouped_by', - isa => GroupedBy, - predicate => 'has_grouped_by', - coerce => 1, - default => sub { $p->static ? [] : undef }, + has 'ordered_by' => ( + is => 'ro', + writer => '_set_ordered_by', + isa => OrderedBy, + predicate => 'has_ordered_by', + coerce => 1, + default => sub { $p->static ? [] : undef }, ); -=attribute_public prefetch is: ro, isa: L +=attribute_public grouped_by -prefetch is passed to ->search to optimize the number of database fetches for joins +Is passed to ->search to determine aggregate results. =cut - has prefetch => - ( - is => 'ro', - writer => '_set_prefetch', - isa => Prefetch, - default => sub { $p->static ? [] : undef }, - coerce => 1, - trigger => sub - { - my ($self, $new) = @_; - if($self->has_prefetch_allows and @{$self->prefetch_allows}) - { - foreach my $pf (@$new) - { - if(HashRef->check($pf)) - { - die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| - unless $self->prefetch_validator->validate($pf)->[0]; - } - else - { - die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| - unless $self->prefetch_validator->validate({$pf => 1})->[0]; - } - } - } - else - { - return if not defined($new); - die 'Prefetching is not allowed' if @$new; - } - }, + has 'grouped_by' => ( + is => 'ro', + writer => '_set_grouped_by', + isa => GroupedBy, + predicate => 'has_grouped_by', + coerce => 1, + default => sub { $p->static ? [] : undef }, ); -=attribute_public prefetch_allows is: ro, isa: ArrayRef[ArrayRef|Str|HashRef] - -prefetch_allows limits what relations may be prefetched when executing searches with joins. This is necessary to avoid denial of service attacks in form of queries which would return a large number of data and unwanted disclosure of data. +=attribute_public prefetch -Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator. - - prefetch_allows => [ 'cds', { cds => tracks }, { cds => producers } ] # to be explicit - prefetch_allows => [ 'cds', { cds => '*' } ] # wildcard means the same thing +Is passed to ->search to optimize the number of database fetches for joins. =cut - has prefetch_allows => - ( - is => 'ro', - writer => '_set_prefetch_allows', - isa => ArrayRef[ArrayRef|Str|HashRef], - default => sub { [ ] }, - predicate => 'has_prefetch_allows', - trigger => sub - { - my ($self, $new) = @_; - - sub check_rel { - my ($self, $rel, $static) = @_; - if(ArrayRef->check($rel)) - { - foreach my $rel_sub (@$rel) - { - $self->check_rel($rel_sub, $static); - } - } - elsif(HashRef->check($rel)) - { - while(my($k,$v) = each %$rel) - { - $self->check_has_relation($k, $v, undef, $static); - } - $self->prefetch_validator->load($rel); + has prefetch => ( + is => 'ro', + writer => '_set_prefetch', + isa => Prefetch, + default => sub { $p->static ? [] : undef }, + coerce => 1, + trigger => sub { + my ( $self, $new ) = @_; + + foreach my $pf (@$new) { + if ( HashRef->check($pf) ) { + die + qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| + unless $self->prefetch_validator->validate($pf)->[0]; } - else - { - $self->check_has_relation($rel, undef, undef, $static); - $self->prefetch_validator->load($rel); + else { + die + qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| + unless $self->prefetch_validator->validate( + { $pf => 1 } )->[0]; } } - - foreach my $rel (@$new) - { - $self->check_rel($rel, $p->static); - } }, ); -=attribute_public search_exposes is: ro, isa: ArrayRef[Str|HashRef] +=attribute_public search_exposes -search_exposes limits what can actually be searched. If a certain column isn't indexed or perhaps a BLOB, you can explicitly say which columns can be search and exclude that one. +Limits what can actually be searched. If a certain column isn't indexed or +perhaps a BLOB, you can explicitly say which columns can be search to exclude +that one. -Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator. +Like the synopsis in DBIC::API shows, you can declare a "template" of what is +allowed (by using '*'). Each element passed in, will be converted into a +Data::DPath and added to the validator. =cut - has 'search_exposes' => - ( - is => 'ro', - writer => '_set_search_exposes', - isa => ArrayRef[Str|HashRef], + has 'search_exposes' => ( + is => 'ro', + writer => '_set_search_exposes', + isa => ArrayRef [ Str | HashRef ], predicate => 'has_search_exposes', - default => sub { [ ] }, - trigger => sub - { - my ($self, $new) = @_; + default => sub { [] }, + trigger => sub { + my ( $self, $new ) = @_; $self->search_validator->load($_) for @$new; }, ); -=attribute_public search is: ro, isa: L +=attribute_public search -search contains the raw search parameters. Upon setting, a trigger will fire to format them, set search_parameters, and set search_attributes. +Contains the raw search parameters. Upon setting, a trigger will fire to format +them, set search_parameters and search_attributes. Please see L for details on how the format works. =cut - has 'search' => - ( - is => 'ro', - writer => '_set_search', - isa => SearchParameters, + has 'search' => ( + is => 'ro', + writer => '_set_search', + isa => SearchParameters, predicate => 'has_search', - coerce => 1, - trigger => sub - { - my ($self, $new) = @_; - - if($self->has_search_exposes and @{$self->search_exposes}) - { - foreach my $foo (@$new) - { - while( my ($k, $v) = each %$foo) - { + coerce => 1, + trigger => sub { + my ( $self, $new ) = @_; + + if ( $self->has_search_exposes and @{ $self->search_exposes } ) { + foreach my $foo (@$new) { + while ( my ( $k, $v ) = each %$foo ) { local $Data::Dumper::Terse = 1; - die qq|{ $k => ${\Dumper($v)} } is not an allowed search term in: ${\join("\n", @{$self->search_validator->templates})}| - unless $self->search_validator->validate({$k=>$v})->[0]; + die + qq|{ $k => ${\Dumper($v)} } is not an allowed search term in: ${\join("\n", @{$self->search_validator->templates})}| + unless $self->search_validator->validate( + { $k => $v } )->[0]; } } } - else - { - foreach my $foo (@$new) - { - while( my ($k, $v) = each %$foo) - { - $self->check_column_relation({$k => $v}); + else { + foreach my $foo (@$new) { + while ( my ( $k, $v ) = each %$foo ) { + $self->check_column_relation( { $k => $v } ); } } } - - my ($search_parameters, $search_attributes) = $self->generate_parameters_attributes($new); + + my ( $search_parameters, $search_attributes ) = + $self->generate_parameters_attributes($new); $self->_set_search_parameters($search_parameters); $self->_set_search_attributes($search_attributes); }, ); -=attribute_public search_parameters is:ro, isa: L +=attribute_public search_parameters -search_parameters stores the formatted search parameters that will be passed to ->search +Stores the formatted search parameters that will be passed to ->search. =cut - has search_parameters => - ( - is => 'ro', - isa => SearchParameters, - writer => '_set_search_parameters', + has search_parameters => ( + is => 'ro', + isa => SearchParameters, + writer => '_set_search_parameters', predicate => 'has_search_parameters', - coerce => 1, - default => sub { [{}] }, + coerce => 1, + default => sub { [ {} ] }, ); -=attribute_public search_attributes is:ro, isa: HashRef +=attribute_public search_attributes -search_attributes stores the formatted search attributes that will be passed to ->search +Stores the formatted search attributes that will be passed to ->search. =cut - has search_attributes => - ( - is => 'ro', - isa => HashRef, - writer => '_set_search_attributes', - predicate => 'has_search_attributes', + has search_attributes => ( + is => 'ro', + isa => HashRef, + writer => '_set_search_attributes', + predicate => 'has_search_attributes', lazy_build => 1, ); -=attribute_public search_total_entries is: ro, isa: Int +=attribute_public search_total_entries -search_total_entries stores the total number of entries in a paged search result +Stores the total number of entries in a paged search result. =cut - has search_total_entries => - ( - is => 'ro', - isa => Int, - writer => '_set_search_total_entries', + has search_total_entries => ( + is => 'ro', + isa => Int, + writer => '_set_search_total_entries', predicate => 'has_search_total_entries', ); -=attribute_public select_exposes is: ro, isa: ArrayRef[Str|HashRef] +=attribute_public select_exposes -select_exposes limits what can actually be selected. Use this to whitelist database functions (such as COUNT). +Limits what can actually be selected. Use this to whitelist database functions +(such as COUNT). -Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator. +Like the synopsis in DBIC::API shows, you can declare a "template" of what is +allowed (by using '*'). Each element passed in, will be converted into a +Data::DPath and added to the validator. =cut - has 'select_exposes' => - ( - is => 'ro', - writer => '_set_select_exposes', - isa => ArrayRef[Str|HashRef], + has 'select_exposes' => ( + is => 'ro', + writer => '_set_select_exposes', + isa => ArrayRef [ Str | HashRef ], predicate => 'has_select_exposes', - default => sub { [ ] }, - trigger => sub - { - my ($self, $new) = @_; + default => sub { [] }, + trigger => sub { + my ( $self, $new ) = @_; $self->select_validator->load($_) for @$new; }, ); -=attribute_public select is: ro, isa: L +=attribute_public select -select is the search attribute that allows you to both limit what is returned in the result set, and also make use of database functions like COUNT. +Is the search attribute that allows you to both limit what is returned in the +result set and also make use of database functions like COUNT. Please see L for more details. =cut - has select => - ( - is => 'ro', - writer => '_set_select', - isa => SelectColumns, + has select => ( + is => 'ro', + writer => '_set_select', + isa => SelectColumns, predicate => 'has_select', - default => sub { $p->static ? [] : undef }, - coerce => 1, - trigger => sub - { - my ($self, $new) = @_; - if($self->has_select_exposes) - { - foreach my $val (@$new) - { + default => sub { $p->static ? [] : undef }, + coerce => 1, + trigger => sub { + my ( $self, $new ) = @_; + if ( $self->has_select_exposes ) { + foreach my $val (@$new) { die "'$val' is not allowed in a select" unless $self->select_validator->validate($val); } } - else - { - $self->check_column_relation($_, $p->static) for @$new; + else { + $self->check_column_relation( $_, $p->static ) for @$new; } }, ); -=attribute_public as is: ro, isa: L +=attribute_public as -as is the search attribute compliment to L that allows you to label columns for object inflaction and actually reference database functions like COUNT. +Is the search attribute compliment to L that allows you to label +columns for object inflaction and actually reference database functions like +COUNT. Please see L for more details. =cut - has as => - ( - is => 'ro', - writer => '_set_as', - isa => AsAliases, + has as => ( + is => 'ro', + writer => '_set_as', + isa => AsAliases, default => sub { $p->static ? [] : undef }, - trigger => sub - { - my ($self, $new) = @_; - if($self->has_select) - { - die "'as' argument count (${\scalar(@$new)}) must match 'select' argument count (${\scalar(@{$self->select || []})})" - unless @$new == @{$self->select || []}; + trigger => sub { + my ( $self, $new ) = @_; + if ( $self->has_select ) { + die + "'as' argument count (${\scalar(@$new)}) must match 'select' argument count (${\scalar(@{$self->select || []})})" + unless @$new == @{ $self->select || [] }; } - elsif(defined $new) - { + elsif ( defined $new ) { die "'as' is only valid if 'select is also provided'"; } } ); -=attribute_public joins is: ro, isa L - -joins holds the top level JoinBuilder object used to keep track of joins automagically while formatting complex search parameters. +=attribute_public joins -Provides a single handle which returns the 'join' attribute for search_attributes: +Holds the top level JoinBuilder object used to keep track of joins automagically +while formatting complex search parameters. - build_joins => 'joins' +Provides the method 'build_joins' which returns the 'join' attribute for +search_attributes. =cut - has joins => - ( - is => 'ro', - isa => JoinBuilder, + has joins => ( + is => 'ro', + isa => JoinBuilder, lazy_build => 1, - handles => - { - build_joins => 'joins', - } + handles => { build_joins => 'joins', } ); -=attribute_public request_data is: ro, isa: HashRef +=attribute_public request_data -request_data holds the raw (but deserialized) data for ths request +Holds the raw (but deserialized) data for this request. =cut - has 'request_data' => - ( - is => 'ro', - isa => HashRef, - writer => '_set_request_data', - trigger => sub - { - my ($self, $new) = @_; + has 'request_data' => ( + is => 'ro', + isa => HashRef, + writer => '_set_request_data', + predicate => 'has_request_data', + trigger => sub { + my ( $self, $new ) = @_; my $controller = $self->_controller; - $self->_set_prefetch($new->{$controller->prefetch_arg}) if exists $new->{$controller->prefetch_arg}; - $self->_set_select($new->{$controller->select_arg}) if exists $new->{$controller->select_arg}; - $self->_set_as($new->{$controller->as_arg}) if exists $new->{$controller->as_arg}; - $self->_set_grouped_by($new->{$controller->grouped_by_arg}) if exists $new->{$controller->grouped_by_arg}; - $self->_set_ordered_by($new->{$controller->ordered_by_arg}) if exists $new->{$controller->ordered_by_arg}; - $self->_set_search($new->{$controller->search_arg}) if exists $new->{$controller->search_arg}; - $self->_set_count($new->{$controller->count_arg}) if exists $new->{$controller->count_arg}; - $self->_set_page($new->{$controller->page_arg}) if exists $new->{$controller->page_arg}; + return unless defined($new) && keys %$new; + $self->_set_prefetch( $new->{ $controller->prefetch_arg } ) + if exists $new->{ $controller->prefetch_arg }; + $self->_set_select( $new->{ $controller->select_arg } ) + if exists $new->{ $controller->select_arg }; + $self->_set_as( $new->{ $controller->as_arg } ) + if exists $new->{ $controller->as_arg }; + $self->_set_grouped_by( $new->{ $controller->grouped_by_arg } ) + if exists $new->{ $controller->grouped_by_arg }; + $self->_set_ordered_by( $new->{ $controller->ordered_by_arg } ) + if exists $new->{ $controller->ordered_by_arg }; + $self->_set_count( $new->{ $controller->count_arg } ) + if exists $new->{ $controller->count_arg }; + $self->_set_page( $new->{ $controller->page_arg } ) + if exists $new->{ $controller->page_arg }; + $self->_set_offset( $new->{ $controller->offset_arg } ) + if exists $new->{ $controller->offset_arg }; + $self->_set_search( $new->{ $controller->search_arg } ) + if exists $new->{ $controller->search_arg }; } ); - method _build_joins => sub { return Catalyst::Controller::DBIC::API::JoinBuilder->new(name => 'TOP') }; + method _build_joins => sub { + return Catalyst::Controller::DBIC::API::JoinBuilder->new( + name => 'TOP' ); + }; =method_protected format_search_parameters -format_search_parameters iterates through the provided params ArrayRef, calling generate_column_parameters on each one +Iterates through the provided arrayref calling generate_column_parameters on +each one. =cut - method format_search_parameters => sub - { - $DB::single = 1; - my ($self, $params) = @_; - + method format_search_parameters => sub { + my ( $self, $params ) = @_; + my $genparams = []; - foreach my $param (@$params) - { - push(@$genparams, $self->generate_column_parameters($self->stored_result_source, $param, $self->joins)); + foreach my $param (@$params) { + push( + @$genparams, + $self->generate_column_parameters( + $self->stored_result_source, + $param, $self->joins + ) + ); } return $genparams; @@ -488,42 +433,87 @@ format_search_parameters iterates through the provided params ArrayRef, calling =method_protected generate_column_parameters -generate_column_parameters recursively generates properly aliased parameters for search, building a new JoinBuilder each layer of recursion +Recursively generates properly aliased parameters for search building a new +JoinBuilder each layer of recursion. =cut - method generate_column_parameters => sub - { - $DB::single = 1; - my ($self, $source, $param, $join, $base) = @_; + method generate_column_parameters => sub { + my ( $self, $source, $param, $join, $base ) = @_; $base ||= 'me'; - my $search_params; + my $search_params = {}; + + # return non-hashref params unaltered + return $param + unless ref $param eq 'HASH'; # build up condition - foreach my $column (keys %$param) - { - if($source->has_relationship($column)) - { - unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH') + foreach my $column ( keys %$param ) { + my $value = $param->{$column}; + if ( $source->has_relationship($column) ) { + + # check if the value isn't a hashref + unless ( ref $value eq 'HASH' ) { - $search_params->{join('.', $base, $column)} = $param->{$column}; + $search_params->{ join( '.', $base, $column ) } = + $value; next; } - %$search_params = - %{ - $self->generate_column_parameters - ( - $source->related_source($column), - $param->{$column}, - Catalyst::Controller::DBIC::API::JoinBuilder->new(parent => $join, name => $column), - $column - ) + $search_params = { + %$search_params, + %{ $self->generate_column_parameters( + $source->related_source($column), + $value, + Catalyst::Controller::DBIC::API::JoinBuilder->new( + parent => $join, + name => $column + ), + $column + ) + } }; } - else - { - $search_params->{join('.', $base, $column)} = $param->{$column}; + elsif ( $source->has_column($column) ) { + $search_params->{ join( '.', $base, $column ) } = + $param->{$column}; + } + elsif ( $column eq '-or' || $column eq '-and' || $column eq '-not' ) { + # either an arrayref or hashref + if ( ref $value eq 'HASH' ) { + $search_params->{$column} = $self->generate_column_parameters( + $source, + $value, + $join, + $base, + ); + } + elsif ( ref $value eq 'ARRAY' ) { + push @{$search_params->{$column}}, + $self->generate_column_parameters( + $source, + $_, + $join, + $base, + ) + for @$value; + } + else { + die "unsupported value '$value' for column '$column'\n"; + } + } + + # might be a sql function instead of a column name + # e.g. {colname => {like => '%foo%'}} + else { + # but only if it's not a hashref + unless ( ref $value eq 'HASH' ) { + $search_params->{ join( '.', $base, $column ) } = + $param->{$column}; + } + else { + die "unsupported value '$value' for column '$column'\n"; + } } } @@ -532,79 +522,111 @@ generate_column_parameters recursively generates properly aliased parameters for =method_protected generate_parameters_attributes -generate_parameters_attributes takes the raw search arguments and formats the parameters by calling format_search_parameters. Then builds the related attributes, preferring request-provided arguments for things like grouped_by over statically configured options. Finally tacking on the appropriate joins. Returns both formatted search parameters and the search attributes. +Takes the raw search arguments and formats them by calling +format_search_parameters. Then builds the related attributes, preferring +request-provided arguments for things like grouped_by over statically configured +options. Finally tacking on the appropriate joins. + +Returns a list of both formatted search parameters and attributes. =cut - method generate_parameters_attributes => sub - { - $DB::single = 1; - my ($self, $args) = @_; + method generate_parameters_attributes => sub { + my ( $self, $args ) = @_; - return ( $self->format_search_parameters($args), $self->search_attributes ); + return ( $self->format_search_parameters($args), + $self->search_attributes ); }; -=method_protected _build_search_attributes - -This builder method generates the search attributes - -=cut - - method _build_search_attributes => sub - { - my ($self, $args) = @_; - my $static = $self->_controller; - my $search_attributes = - { - group_by => $self->grouped_by || ((scalar(@{$static->grouped_by})) ? $static->grouped_by : undef), - order_by => $self->ordered_by || ((scalar(@{$static->ordered_by})) ? $static->ordered_by : undef), - select => $self->select || ((scalar(@{$static->select})) ? $static->select : undef), - as => $self->as || ((scalar(@{$static->as})) ? $static->as : undef), + method _build_search_attributes => sub { + my ( $self, $args ) = @_; + my $static = $self->_controller; + my $search_attributes = { + group_by => $self->grouped_by + || ( + ( scalar( @{ $static->grouped_by } ) ) ? $static->grouped_by + : undef + ), + order_by => $self->ordered_by + || ( + ( scalar( @{ $static->ordered_by } ) ) ? $static->ordered_by + : undef + ), + select => $self->select + || ( + ( scalar( @{ $static->select } ) ) ? $static->select + : undef + ), + as => $self->as + || ( ( scalar( @{ $static->as } ) ) ? $static->as : undef ), prefetch => $self->prefetch || $static->prefetch || undef, - rows => $self->count || $static->count, - page => $self->page, - join => $self->build_joins, + rows => $self->count || $static->count, + page => $static->page, + offset => $self->offset, + join => $self->build_joins, }; - $search_attributes = - { - map { @$_ } - grep - { - defined($_->[1]) - ? - (ref($_->[1]) && reftype($_->[1]) eq 'HASH' && keys %{$_->[1]}) - || (ref($_->[1]) && reftype($_->[1]) eq 'ARRAY' && @{$_->[1]}) - || length($_->[1]) - : - undef - } - map { [$_, $search_attributes->{$_}] } - keys %$search_attributes - }; + if ( $self->has_page ) { + $search_attributes->{page} = $self->page; + } + elsif (!$self->has_page + && defined( $search_attributes->{offset} ) + && defined( $search_attributes->{rows} ) ) + { + $search_attributes->{page} = + $search_attributes->{offset} / $search_attributes->{rows} + 1; + delete $search_attributes->{offset}; + } + $search_attributes = { + map {@$_} + grep { + defined( $_->[1] ) + ? ( ref( $_->[1] ) + && reftype( $_->[1] ) eq 'HASH' + && keys %{ $_->[1] } ) + || ( ref( $_->[1] ) + && reftype( $_->[1] ) eq 'ARRAY' + && @{ $_->[1] } ) + || length( $_->[1] ) + : undef + } + map { [ $_, $search_attributes->{$_} ] } + keys %$search_attributes + }; - if ($search_attributes->{page} && !$search_attributes->{rows}) { + if ( $search_attributes->{page} && !$search_attributes->{rows} ) { die 'list_page can only be used with list_count'; } - - if ($search_attributes->{select}) { + + if ( $search_attributes->{select} ) { + # make sure all columns have an alias to avoid ambiguous issues # but allow non strings (eg. hashrefs for db procs like 'count') # to pass through unmolested - $search_attributes->{select} = [map { (Str->check($_) && $_ !~ m/\./) ? "me.$_" : $_ } (ref $search_attributes->{select}) ? @{$search_attributes->{select}} : $search_attributes->{select}]; + $search_attributes->{select} = [ + map { ( Str->check($_) && $_ !~ m/\./ ) ? "me.$_" : $_ } + ( ref $search_attributes->{select} ) + ? @{ $search_attributes->{select} } + : $search_attributes->{select} + ]; } return $search_attributes; - + }; }; + =head1 DESCRIPTION -RequestArguments embodies those arguments that are provided as part of a request or effect validation on request arguments. This Role can be consumed in one of two ways. As this is a parameterized Role, it accepts a single argument at composition time: 'static'. This indicates that those parameters should be stored statically and used as a fallback when the current request doesn't provide them. +RequestArguments embodies those arguments that are provided as part of a request +or effect validation on request arguments. This Role can be consumed in one of +two ways. As this is a parameterized Role, it accepts a single argument at +composition time: 'static'. This indicates that those parameters should be +stored statically and used as a fallback when the current request doesn't +provide them. =cut - 1;