File Coverage

blib/lib/LINQ/Util.pm
Criterion Covered Total %
statement 17 17 100.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod 3 3 100.0
total 27 27 100.0


line stmt bran cond sub pod time code
1 3     3   2776 use 5.006;
  3         11  
2 3     3   21 use strict;
  3         7  
  3         80  
3 3     3   14 use warnings;
  3         6  
  3         227  
4              
5             package LINQ::Util;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.002';
9              
10 3     3   21 use Exporter::Shiny qw( field fields check_fields );
  3         5  
  3         33  
11              
12             sub fields {
13 10     10 1 13265 require LINQ::FieldSet::Selection;
14 10         42 'LINQ::FieldSet::Selection'->new( @_ );
15             }
16              
17             sub field {
18 4     4 1 889 require LINQ::FieldSet::Single;
19 4         29 'LINQ::FieldSet::Single'->new( @_ );
20             }
21              
22             sub check_fields {
23 35     35 1 6774 require LINQ::FieldSet::Assertion;
24 35         178 'LINQ::FieldSet::Assertion'->new( @_ );
25             }
26              
27             1;
28              
29             __END__
30              
31             =pod
32              
33             =encoding utf-8
34              
35             =head1 NAME
36              
37             LINQ::Util - useful utilities to make working with LINQ collections easier
38              
39             =head1 SYNOPSIS
40              
41             use feature qw( say );
42             use LINQ qw( LINQ )';
43             use LINQ::Util qw( fields );
44            
45             my $collection = LINQ( [
46             { name => 'Alice', age => 30, dept => 'IT' },
47             { name => 'Bob' , age => 29, dept => 'IT' },
48             { name => 'Carol', age => 32, dept => 'Marketing' },
49             { name => 'Dave', age => 33, dept => 'Accounts' },
50             ] );
51            
52             my $name_and_dept = $collection->select( fields( 'name', 'dept' ) );
53            
54             for ( $name_and_dept->to_list ) {
55             printf( "Hi, I'm %s from %s\n", $_->name, $_->dept );
56             }
57              
58             =head1 DESCRIPTION
59              
60             LINQ::Util provides a collection of auxiliary functions to make working with
61             LINQ collections a little more intuitive and perhaps avoid passing a bunch of
62             C<< sub { ... } >> arguments to C<select> and C<where>.
63              
64             =head1 FUNCTIONS
65              
66             =over
67              
68             =item C<< fields( SPEC ) >>
69              
70             Creates a coderef (actually a blessed object overloading C<< &{} >>) which
71             takes a hashref or object as input, selects just the fields/keys given in the
72             SPEC, and returns an object with those fields.
73              
74             A simple example would be:
75              
76             my $selector = fields( 'name' );
77             my $object = $selector->( { name => 'Bob', age => 29 } );
78              
79             In this example, C<< $object >> would be a blessed object with a C<name>
80             method which returns "Bob".
81              
82             Fields can be renamed:
83              
84             my $selector = fields( 'name', -as => 'moniker' );
85             my $object = $selector->( { name => 'Bob', age => 29 } );
86             say $object->moniker; # ==> "Bob"
87              
88             A coderef can be used as a field:
89              
90             my $selector = fields(
91             sub { uc( $_->{'name'} ) }, -as => 'moniker',
92             );
93             my $object = $selector->( { name => 'Bob', age => 29 } );
94             say $object->moniker; # ==> "BOB"
95              
96             An asterisk field selects all the input fields:
97              
98             my $selector = fields(
99             sub { uc( $_->{'name'} ) }, -as => 'moniker',
100             '*',
101             );
102             my $object = $selector->( { name => 'Bob', age => 29 } );
103             say $object->moniker; # ==> "BOB"
104             say $object->name; # ==> "Bob"
105             say $object->age; # ==> 29
106              
107             The aim of the C<fields> function is to allow the LINQ C<select> method to
108             function more like an SQL SELECT, where you give a list of fields you wish
109             to select.
110              
111             =item C<< field( NAME ) >>
112              
113             Conceptually similar to C<< fields() >> but for a single field. Returns the
114             field value instead of a hashref of field values.
115              
116             my $field = field('name');
117             say $field->( $_ ) for (
118             { name => 'Alice' },
119             { name => 'Bob' },
120             );
121              
122             If called in list context with extra arguments after the field name, a list
123             will be returned, including the extra arguments unchanged.
124              
125             my $people = LINQ( [
126             { name => 'Alice', age => 30, dept => 3 },
127             { name => 'Bob' , age => 29, dept => 3 },
128             { name => 'Carol', age => 32, dept => 4 },
129             { name => 'Dave', age => 33, dept => 1 },
130             ] );
131            
132             my $depts = LINQ( [
133             { id => 3, name => 'IT' },
134             { id => 4, name => 'Marketing' },
135             { id => 1, name => 'Accounts' },
136             ] );
137            
138             my $joiner = sub {
139             my ( $person, $dept ) = @_;
140             return {
141             person_name => $person->{name},
142             person_age => $person->{age},
143             dept_name => $dept->{name},
144             };
145             };
146            
147             my $joined = $people->join( $depts, field 'dept', field 'id', $joiner );
148            
149             print Dumper( $joined->to_array );
150              
151             =item C<< check_fields( SPEC ) >>
152              
153             If C<< fields() >> can be compared to SQL SELECT, then C<< check_fields() >>
154             can be compared to SQL WHERE. Like C<< fields() >> it assumes your data is
155             hashrefs or blessed objects with attributes.
156              
157             # Select people called Bob.
158             $people
159             ->where( check_fields( 'name', -is => 'Bob' ) )
160             ->select( fields( 'name', 'age', 'dept' ) );
161              
162             Different operators can be used. Whether performing string or numeric
163             comparison, ">", "<", ">=", "<=", "==", and "!=" are used. (And the C<< -is >>
164             parameter is used to provide the right hand side of the comparison, even
165             for comparisons like "!=".)
166              
167             $people
168             ->where( check_fields( 'name', -cmp => '>', -is => 'Bob' ) );
169              
170             C<< check_fields() >> will probably guess correctly whether you want numeric
171             or string comparison, but if you need to specify, you can:
172              
173             $people
174             ->where( check_fields( 'phone', -is => '012345679', -string );
175            
176             $people
177             ->where( check_fields( 'age', -is => '33', -numeric );
178              
179             String comparisons can be made case-insensitive:
180              
181             $people
182             ->where( check_fields( 'name', -is => 'Bob', -nocase ) );
183              
184             You can use C<< -in >> to find a value in an arrayref. These comparisons are
185             always stringy and case-sensitive.
186              
187             $people
188             ->where( check_fields( 'name', -in => ['Alice', 'Bob'] ) );
189              
190             You can invert any comparison using C<< -nix >>.
191              
192             $people
193             ->where( check_fields( 'name', -nix, -in => ['Alice', 'Bob'] ) );
194              
195             You can perform more complex matches using L<match::simple>:
196              
197             $people
198             ->where( check_fields( 'name', -match => qr/^[RB]ob(ert)?$/i ) );
199              
200             SQL LIKE is also supported:
201              
202             $people
203             ->where( check_fields( 'name', -like => 'Bob%', -nocase ) );
204              
205             You can check multiple fields at once. There's an implied "AND" between the
206             conditions.
207              
208             $people
209             ->where( check_fields(
210             'name', -is => 'Bob',
211             'age', -nix, -is => 33,
212             ) );
213              
214             You can compare one field to another field using C<< -to >>:
215              
216             # Says all the values which are between the min and max.
217             LINQ(
218             { min => 10, max => 100, value => 50 },
219             { min => 10, max => 100, value => 5 },
220             { min => 10, max => 20, value => 50 },
221             )->where( check_fields(
222             'value', -cmp => '>=', -to => 'min', -numeric,
223             'value', -cmp => '<=', -to => 'max', -numeric,
224             ) )->foreach( sub {
225             say $_->value;
226             } );
227              
228             You can invert a whole C<< check_fields() >> using the C<< not >> method:
229              
230             my $where_not_bob = check_fields( 'name', -is => 'Bob' )->not;
231            
232             $people->where( $where_not_bob );
233              
234             Generally, you can use C<< not >>, C<< and >>, and C<< or >> methods to compose
235             more complex conditions. The C<< ~ >>, C<< & >>, and C<< | >> bitwise operators
236             are also overloaded to compose conditions.
237              
238             my $where_alice = check_fields( 'name', -is => 'Alice' );
239             my $where_bob = check_fields( 'name', -is => 'Bob' );
240            
241             my $where_alice_or_bob = $where_alice->or( $where_bob );
242            
243             # Or...
244             my $where_alice_or_bob = $where_alice | $where_bob;
245            
246             # Or...
247             my $where_alice_or_bob =
248             check_fields( 'name', -is => 'Alice' )
249             ->or( 'name', -is => 'Bob' );
250              
251             Like with C<< fields() >>, fields can be a coderef.
252              
253             my $where_bob = check_fields(
254             sub { $_->get_name("givenName") }, -is => 'Bob'
255             );
256              
257             =back
258              
259             =head1 BUGS
260              
261             Please report any bugs to
262             L<http://rt.cpan.org/Dist/Display.html?Queue=LINQ>.
263              
264             =head1 SEE ALSO
265              
266             L<LINQ::Collection>, L<LINQ>.
267              
268             L<https://en.wikipedia.org/wiki/Language_Integrated_Query>
269              
270             =head1 AUTHOR
271              
272             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
273              
274             =head1 COPYRIGHT AND LICENCE
275              
276             This software is copyright (c) 2021 by Toby Inkster.
277              
278             This is free software; you can redistribute it and/or modify it under
279             the same terms as the Perl 5 programming language system itself.
280              
281             =head1 DISCLAIMER OF WARRANTIES
282              
283             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
284             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
285             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.