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