Typos in Doku
[kivitendo-erp.git] / SL / DB / Helper / Sorted.pm
1 package SL::DB::Helper::Sorted;
2
3 use strict;
4
5 require Exporter;
6 our @ISA    = qw(Exporter);
7 our @EXPORT = qw(get_all_sorted make_sort_string);
8
9 my %sort_spec;
10
11 sub make_sort_string {
12   my ($class, %params) = @_;
13
14   my $sort_spec        = _get_sort_spec($class);
15
16   my $sort_dir         = defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{default}->[1];
17   my $sort_dir_str     = $sort_dir ? 'ASC' : 'DESC';
18
19   my $sort_by          = $params{sort_by};
20   $sort_by             = $sort_spec->{default}->[0] unless $sort_spec->{columns}->{$sort_by};
21
22   my $nulls_str        = '';
23   if ($sort_spec->{nulls}) {
24     $nulls_str = ref($sort_spec->{nulls}) ? ($sort_spec->{nulls}->{$sort_by} || $sort_spec->{nulls}->{default}) : $sort_spec->{nulls};
25     $nulls_str = " NULLS ${nulls_str}" if $nulls_str;
26   }
27
28   my $sort_by_str = $sort_spec->{columns}->{$sort_by};
29   $sort_by_str    = [ $sort_by_str ] unless ref($sort_by_str) eq 'ARRAY';
30   $sort_by_str    = join(', ', map { "${_} ${sort_dir_str}${nulls_str}" } @{ $sort_by_str });
31
32   return wantarray ? ($sort_by, $sort_dir, $sort_by_str) : $sort_by_str;
33 }
34
35 sub get_all_sorted {
36   my ($class, %params) = @_;
37   my $sort_str         = $class->make_sort_string(sort_by => delete($params{sort_by}), sort_dir => delete($params{sort_dir}));
38
39   return $class->get_all(sort_by => $sort_str, %params);
40 }
41
42 sub _get_sort_spec {
43   my ($class) = @_;
44   return $sort_spec{$class} ||= _make_sort_spec($class);
45 }
46
47 sub _make_sort_spec {
48   my ($class) = @_;
49
50   my %sort_spec = defined &{ "${class}::_sort_spec" } ? $class->_sort_spec : ();
51
52   my $meta = $class->object_class->meta;
53
54   if (!$sort_spec{default}) {
55     my @primary_keys = $meta->primary_key;
56     $sort_spec{default} = [ "" . $primary_keys[0], 1 ];
57   }
58
59   $sort_spec{columns} ||= { SIMPLE => [ map { "$_" } $meta->columns ] };
60
61   if ($sort_spec{columns}->{SIMPLE}) {
62     my $table = $meta->table;
63
64     if (!ref($sort_spec{columns}->{SIMPLE}) && ($sort_spec{columns}->{SIMPLE} eq 'ALL')) {
65       map { $sort_spec{columns}->{"$_"} ||= "${table}.${_}"} @{ $meta->columns };
66       delete $sort_spec{columns}->{SIMPLE};
67     } else {
68       map { $sort_spec{columns}->{$_} = "${table}.${_}" } @{ delete($sort_spec{columns}->{SIMPLE}) };
69     }
70   }
71
72   return \%sort_spec;
73 }
74
75 1;
76
77 __END__
78
79 =encoding utf8
80
81 =head1 NAME
82
83 SL::DB::Helper::Sorted - Mixin for a manager class that handles
84 sorting of database records
85
86 =head1 SYNOPSIS
87
88   package SL::DB::Manager::Message;
89
90   use SL::DB::Helper::Sorted;
91
92   sub _sort_spec {
93     return ( columns => { recipient_id => [ 'CASE
94                                              WHEN recipient_group_id IS NULL THEN lower(recipient.name)
95                                              ELSE                                 lower(recipient_group.name)
96                                              END',                                      ],
97                           sender_id    => [ 'lower(sender.name)',                       ],
98                           created_at   => [ 'created_at',                               ],
99                           subject      => [ 'lower(subject)',                           ],
100                           status       => [ 'NOT COALESCE(unread, FALSE)', 'created_at' ],
101                         },
102              default => [ 'status', 1 ],
103              nulls   => { default => 'LAST',
104                           subject => 'FIRST',
105                         }
106            );
107   }
108
109   package SL::Controller::Message;
110
111   sub action_list {
112     my $messages = SL::DB::Manager::Message->get_all_sorted(sort_by  => $::form->{sort_by},
113                                                             sort_dir => $::form->{sort_dir});
114   }
115
116 =head1 CLASS FUNCTIONS
117
118 =over 4
119
120 =item C<make_sort_string %params>
121
122 Evaluates C<$params{sort_by}> and C<$params{sort_dir}> and returns an
123 SQL string suitable for sorting. The package this package is mixed
124 into has to provide a method L</_sort_spec> that returns a hash whose
125 structure is explained below. That hash is authoritative in which
126 columns may be sorted, which column to sort by by default and how to
127 handle C<NULL> values.
128
129 Returns the SQL string in scalar context. In array context it returns
130 three values: the actual column it sorts by (suitable for another call
131 to L</make_sort_string>), the actual sort direction (either 0 or 1)
132 and the SQL string.
133
134 =item C<get_all_sorted %params>
135
136 Returns C<< $class->get_all >> with C<sort_by> set to the value
137 returned by c<< $class->make_sort_string(%params) >>.
138
139 =back
140
141 =head1 CLASS FUNCTIONS PROVIDED BY THE MIXING PACKAGE
142
143 =over 4
144
145 =item C<_sort_spec>
146
147 This method is actually not part of this package but can be provided
148 by the package this helper is mixed into. If it isn't then all columns
149 of the corresponding table (as returned by the model's meta data) will
150 be eligible for sorting.
151
152 Returns a hash with the following keys:
153
154 =over 2
155
156 =item C<default>
157
158 A two-element array containing the name and direction by which to sort
159 in default cases. Example:
160
161   default => [ 'name', 1 ],
162
163 Defaults to the table's primary key column (the first column if the
164 primary key is composited).
165
166 =item C<columns>
167
168 A hash reference. Its keys are column names, and its values are SQL
169 strings by which to sort. Example:
170
171   columns => { SIMPLE                  => [ 'transaction_description', 'orddate' ],
172                the_date                => 'CASE WHEN oe.quotation THEN oe.quodate ELSE oe.orddate END',
173                customer_name           => 'lower(customer.name)',
174              },
175
176 If sorting is requested for a column that is not a key in this hash
177 then the default column name will be used.
178
179 The value can be either a scalar or an array reference. If it's the
180 latter then both the sort direction as well as the null handling will
181 be appended to each of its members.
182
183 The special key C<SIMPLE> can be a scalar or an array reference. If it
184 is an array reference then it contains column names that are mapped
185 1:1 onto the table's columns. If it is the scalar 'ALL' then all
186 columns in that model's meta data are mapped 1:1 unless the C<columns>
187 hash already contains a key for that column.
188
189 If C<columns> is missing then all columns of the model will be
190 eligible for sorting. The list of columns is looked up in the model's
191 meta data.
192
193 =item C<nulls>
194
195 Either a scalar or a hash reference determining where C<NULL> values
196 will be sorted. If undefined then the decision is left to the
197 database.
198
199 If it is a scalar then the same value will be used for all
200 classes. The value is either C<FIRST> or C<LAST>.
201
202 If it is a hash reference then its keys are column names (not SQL
203 names). The values are either C<FIRST> or C<LAST>. If a column name is
204 not found in this hash then the special key C<default> will be looked
205 up and used if it is found.
206
207 Example:
208
209   nulls => { transaction_description => 'FIRST',
210              customer_name           => 'FIRST',
211              default                 => 'LAST',
212            },
213
214 =back
215
216 =back
217
218 =head1 BUGS
219
220 Nothing here yet.
221
222 =head1 AUTHOR
223
224 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
225
226 =cut