From 1264cff68a2e27855c34fc2e00e1f6724ab004f4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sven=20Sch=C3=B6ling?= Date: Fri, 24 May 2013 19:14:07 +0200 Subject: [PATCH] ParseFilter auf Objektdispatch erweitert --- SL/Controller/Helper/ParseFilter.pm | 84 +++++++++++++++++++++++----- t/controllers/helpers/parse_filter.t | 59 ++++++++++++++++++- 2 files changed, 128 insertions(+), 15 deletions(-) diff --git a/SL/Controller/Helper/ParseFilter.pm b/SL/Controller/Helper/ParseFilter.pm index 5a1c93cd9..8ccd83401 100644 --- a/SL/Controller/Helper/ParseFilter.pm +++ b/SL/Controller/Helper/ParseFilter.pm @@ -95,13 +95,46 @@ sub _parse_filter { my @result; for (my $i = 0; $i < scalar @$flattened; $i += 2) { my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]); + ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/, { %filters, %{ $params{filters} || {} } }); ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } }); + ($key, $value) = _dispatch_custom_filters($params{class}, $key, $value) if $params{class}; + push @result, $key, $value; } return \@result; } +sub _dispatch_custom_filters { + my ($class, $key, $value) = @_; + + # the key should by now have no filters left + # if it has, catch it here: + die 'unrecognized filters' if $key =~ /:/; + + my @tokens = split /\./, $key; + my $last_token = pop @tokens; + my $curr_class = $class->object_class; + + for my $token (@tokens) { + eval { + $curr_class = $curr_class->meta->relationship($token)->class; + 1; + } or do { + require Carp; + Carp::croak("Could not resolve the relationship '$token' in '$key' while building the filter request"); + } + } + + my $manager = $curr_class->meta->convention_manager->auto_manager_class_name; + + if ($manager->can('filter')) { + ($key, $value) = $manager->filter($last_token, $value, join '.', @tokens, ''); + } + + return ($key, $value); +} + sub _collapse_indirect_filters { my ($flattened) = @_; @@ -227,19 +260,6 @@ As a rule all value filters require a single colon and must be placed before match method suffixes, which are appended with 2 colons. See below for a full list of modifiers. -The reason for the method being last is that it is possible to specify the -method in another input. Suppose you want a date input and a separate -before/after/equal select, you can use the following: - - [% L.date_tag('filter.appointed_date:date', ... ) %] - -and later - - [% L.select_tag('filter.appointed_date::', ... ) %] - -The special empty method will be used to set the method for the previous -method-less input. - =back =head1 LAUNDERING @@ -273,6 +293,42 @@ like this: 'closed => '1', } +=head1 INDIRECT FILTER METHODS + +The reason for the method being last is that it is possible to specify the +method in another input. Suppose you want a date input and a separate +before/after/equal select, you can use the following: + + [% L.date_tag('filter.appointed_date:date', ... ) %] + +and later + + [% L.select_tag('filter.appointed_date:date::', ... ) %] + +The special empty method will be used to set the method for the previous +method-less input. + +=head1 CUSTOM FILTERS FROM OBJECTS + +If the L call contains a parameter C, custom filters will +be honored. Suppose you have added a custom filter 'all' for parts which +expands to search both description and partnumber, the following + + $filter = { + 'part.all:substr::ilike' => 'A1', + } + +will expand to: + + query => [ + or => [ + part.description => { ilike => '%A1%' }, + part.partnumber => { ilike => '%A1%' }, + ] + ] + +For more abuot custom filters, see L. + =head1 FILTERS (leading with :) The following filters are built in, and can be used. @@ -334,7 +390,7 @@ following will not work as you expect: L.input_tag('customer.name:substr::ilike', ...) L.input_tag('invoice.customer.name:substr::ilike', ...) -This will sarch for orders whoe invoice has the _same_ customer, which matches +This will sarch for orders whose invoice has the _same_ customer, which matches both inputs. This is because tables are aliased by their name and not by their position in with_objects. diff --git a/t/controllers/helpers/parse_filter.t b/t/controllers/helpers/parse_filter.t index 1bc3a00b8..9bcbeb8eb 100644 --- a/t/controllers/helpers/parse_filter.t +++ b/t/controllers/helpers/parse_filter.t @@ -1,12 +1,14 @@ use lib 't'; -use Test::More tests => 18; +use Test::More tests => 23; use Test::Deep; use Data::Dumper; use_ok 'Support::TestSetup'; use_ok 'SL::Controller::Helper::ParseFilter'; +use SL::DB::OrderItem; + undef *::any; # Test::Deep exports any (for junctions) and MoreCommon exports any (like in List::Moreutils) Support::TestSetup::login(); @@ -190,3 +192,58 @@ test { with_objects => bag('order.customer', 'order'), }, 'sub objects have to retain their prefix'; +### class filter dispatch +# +test { + name => 'Test', + whut => 'moof', +}, { + query => bag( + name => 'Test', + whut => 'moof' + ), +}, 'object test simple', class => 'SL::DB::Manager::Part'; + +test { + 'type' => 'assembly', +}, { + query => [ + 'assembly' => 1 + ], +}, 'object test without prefix', class => 'SL::DB::Manager::Part'; + +test { + 'part.type' => 'assembly', +}, { + query => [ + 'part.assembly' => 1 + ], +}, 'object test with prefix', class => 'SL::DB::Manager::OrderItem'; + +test { + 'type' => [ 'part', 'assembly' ], +}, { + query => [ + or => [ + and => [ or => [ assembly => 0, assembly => undef ], + "!inventory_accno_id" => 0, + "!inventory_accno_id" => undef, + ], + assembly => 1, + ] + ], +}, 'object test without prefix but complex value', class => 'SL::DB::Manager::Part'; + +test { + 'part.type' => [ 'part', 'assembly' ], +}, { + query => [ + or => [ + and => [ or => [ 'part.assembly' => 0, 'part.assembly' => undef ], + "!part.inventory_accno_id" => 0, + "!part.inventory_accno_id" => undef, + ], + 'part.assembly' => 1, + ] + ], +}, 'object test with prefix but complex value', class => 'SL::DB::Manager::OrderItem'; -- 2.20.1