);
my %methods = (
- lt => sub { +{ lt => $_[0] } },
- gt => sub { +{ gt => $_[0] } },
- ilike => sub { +{ ilike => $_[0] } },
- like => sub { +{ like => $_[0] } },
enable => sub { ;;;; },
+ map {
+ # since $_ is an alias it can't be used in a closure. even "".$_ or "$_"
+ # does not work, we need a real copy.
+ my $_copy = "$_";
+ $_ => sub { +{ $_copy => $_[0] } },
+ } qw(similar match imatch regex regexp like ilike rlike is is_not ne eq lt gt le ge),
);
sub parse_filter {
my $query = _parse_filter($flattened, %params);
- _launder_keys($filter) unless $params{no_launder};
+ _launder_keys($filter, $params{launder_to}) unless $params{no_launder};
return
- query => $query,
- @$objects ? ( with_objects => [ uniq @$objects ]) : ();
+ ($query && @$query ? (query => $query) : ()),
+ ($objects && @$objects ? ( with_objects => [ uniq @$objects ]) : ());
}
sub _launder_keys {
- my ($filter) = @_;
+ my ($filter, $launder_to) = @_;
+ $launder_to ||= $filter;
return unless ref $filter eq 'HASH';
- my @keys = keys %$filter;
- for my $key (@keys) {
+ for my $key (keys %$filter) {
my $orig = $key;
$key =~ s/:/_/g;
- $filter->{$key} = $filter->{$orig};
- _launder_keys($filter->{$key});
+ if ('' eq ref $filter->{$orig}) {
+ $launder_to->{$key} = $filter->{$orig};
+ } elsif ('ARRAY' eq ref $filter->{$orig}) {
+ $launder_to->{$key} = [ @{ $filter->{$orig} } ];
+ } else {
+ $launder_to->{$key} ||= { };
+ _launder_keys($filter->{$key}, $launder_to->{$key});
+ }
};
-
- return $filter;
}
sub _pre_parse {
my ($filter, $with_objects, $prefix, %params) = @_;
- return () unless 'HASH' eq ref $filter;
- $with_objects = [];
+ return (undef, $with_objects) unless 'HASH' eq ref $filter;
+ $with_objects ||= [];
my @result;
if ('HASH' eq ref $value) {
my ($query, $more_objects) = _pre_parse($value, $with_objects, _prefix($prefix, $key));
push @result, @$query if $query;
- push @$with_objects, $key, ($more_objects ? @$more_objects : ());
+ push @$with_objects, _prefix($prefix, $key), ($more_objects ? @$more_objects : ());
} else {
push @result, _prefix($prefix, $key) => $value;
}
customer. L<Rose::DB::Object> alloes you to search for these by filtering them prefixed with their table:
query => [
- customer.name => 'John Doe',
- department.description => [ ilike => '%Sales%' ],
- orddate => [ lt => DateTime->today ],
+ 'customer.name' => 'John Doe',
+ 'department.description' => [ ilike => '%Sales%' ],
+ 'orddate' => [ lt => DateTime->today ],
]
Unfortunately, if you specify them in you form as these strings, the form
=over 4
-=item parse_amount \%FILTER, [ %PARAMS ]
+=item C<parse_filter \%FILTER, [ %PARAMS ]>
First argument is the filter from form. It is highly recommended that you put
all filter attributes into a named container as to not confuse them with the
example if you search for L<Order>s, this input will search for those with a
specific L<Salesman>:
- [% L.select_tag('filter.salesman.id', ...
+ [% L.select_tag('filter.salesman.id', ...) %]
Additionally you can add modifier to the name to set a certain method:
- [% L.input_tag('filter.department.description:substr::ilike' ...
+ [% L.input_tag('filter.department.description:substr::ilike', ...) %]
-This will add the "% .. %" wildcards for substr matching in sql, and add an C<[
-ilike => $value ]> block around it to match case insensitively.
+This will add the "% .. %" wildcards for substr matching in SQL, and add an
+C<< ilike => $value >> block around it to match case insensitively.
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
The special empty method will be used to set the method for the previous
method-less input.
-=item Laundering filter
+=back
+
+=head1 LAUNDERING
-Unfortunately Template cannot parse the postfixes if you want to rerender the
-filter. For this reason all colons filter keys are by default laundered into
-underscores. If you don't want this to happen pass C<no_launder => 1> as a
-parameter. A full select_tag then loks like this:
+Unfortunately Template cannot parse the postfixes if you want to
+rerender the filter. For this reason all colons filter keys are by
+default laundered into underscores, so you can use them like this:
[% L.input_tag('filter.price:number::lt', filter.price_number__lt) %]
+All of your original entries will stay intactg. If you don't want this to
+happen pass C<< no_launder => 1 >> as a parameter. Additionally you can pass a
+different target for the laundered values with the C<launder_to> parameter. It
+takes an hashref and will deep copy all values in your filter to the target. So
+if you have a filter that looks liek this:
-=back
+ $filter = {
+ 'price:number::lt' => '2,30',
+ 'closed => '1',
+ }
+
+and parse it with
+
+ parse_filter($filter, launder_to => $laundered_filter = { })
+
+the original filter will be unchanged, and C<$laundered_filter> will end up
+like this:
+
+ $filter = {
+ 'price_number__lt' => '2,30',
+ 'closed => '1',
+ }
=head1 FILTERS (leading with :)
=item date
-Parses the input string with DateTime->from_lxoffice
+Parses the input string with C<< DateTime->from_lxoffice >>
=item number
-Pasres the input string with Form->parse_amount
+Pasres the input string with C<< Form->parse_amount >>
=item percent
-Parses the input string with Form->parse_amount / 100
+Parses the input string with C<< Form->parse_amount / 100 >>
=item head
following will not work as you expect:
# does not work!
- L.input_tag('customer.name:substr::ilike', ...
- L.input_tag('invoice.customer.name:substr::ilike', ...
+ 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
both inputs. This is because tables are aliased by their name and not by their