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