1 package SL::DB::Helper::Sorted;
 
   6 our @ISA    = qw(Exporter);
 
   7 our @EXPORT = qw(get_all_sorted make_sort_string);
 
  11 sub make_sort_string {
 
  12   my ($class, %params) = @_;
 
  14   my $sort_spec        = _get_sort_spec($class);
 
  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';
 
  19   my $sort_by          = $params{sort_by};
 
  20   $sort_by             = $sort_spec->{default}->[0] unless $sort_spec->{columns}->{$sort_by};
 
  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;
 
  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 });
 
  32   return wantarray ? ($sort_by, $sort_dir, $sort_by_str) : $sort_by_str;
 
  36   my ($class, %params) = @_;
 
  37   my $sort_str         = $class->make_sort_string(sort_by => delete($params{sort_by}), sort_dir => delete($params{sort_dir}));
 
  39   return $class->get_all(sort_by => $sort_str, %params);
 
  44   return $sort_spec{$class} ||= _make_sort_spec($class);
 
  50   my %sort_spec = defined &{ "${class}::_sort_spec" } ? $class->_sort_spec : ();
 
  52   my $meta = $class->object_class->meta;
 
  54   if (!$sort_spec{default}) {
 
  55     my @primary_keys = $meta->primary_key;
 
  56     $sort_spec{default} = [ "" . $primary_keys[0], 1 ];
 
  59   $sort_spec{columns} ||= { SIMPLE => [ map { "$_" } $meta->columns ] };
 
  61   if ($sort_spec{columns}->{SIMPLE}) {
 
  62     my $table = $meta->table;
 
  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};
 
  68       map { $sort_spec{columns}->{$_} = "${table}.${_}" } @{ delete($sort_spec{columns}->{SIMPLE}) };
 
  83 SL::DB::Helper::Sorted - Mixin for a manager class that handles
 
  84 sorting of database records
 
  88   package SL::DB::Manager::Message;
 
  90   use SL::DB::Helper::Sorted;
 
  93     return ( columns => { recipient_id => [ 'CASE
 
  94                                              WHEN recipient_group_id IS NULL THEN lower(recipient.name)
 
  95                                              ELSE                                 lower(recipient_group.name)
 
  97                           sender_id    => [ 'lower(sender.name)',                       ],
 
  98                           created_at   => [ 'created_at',                               ],
 
  99                           subject      => [ 'lower(subject)',                           ],
 
 100                           status       => [ 'NOT COALESCE(unread, FALSE)', 'created_at' ],
 
 102              default => [ 'status', 1 ],
 
 103              nulls   => { default => 'LAST',
 
 109   package SL::Controller::Message;
 
 112     my $messages = SL::DB::Manager::Message->get_all_sorted(sort_by  => $::form->{sort_by},
 
 113                                                             sort_dir => $::form->{sort_dir});
 
 116 =head1 CLASS FUNCTIONS
 
 120 =item C<make_sort_string %params>
 
 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.
 
 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)
 
 134 =item C<get_all_sorted %params>
 
 136 Returns C<< $class->get_all >> with C<sort_by> set to the value
 
 137 returned by c<< $class->make_sort_string(%params) >>.
 
 141 =head1 CLASS FUNCTIONS PROVIDED BY THE MIXING PACKAGE
 
 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.
 
 152 Returns a hash with the following keys:
 
 158 A two-element array containing the name and direction by which to sort
 
 159 in default cases. Example:
 
 161   default => [ 'name', 1 ],
 
 163 Defaults to the table's primary key column (the first column if the
 
 164 primary key is composited).
 
 168 A hash reference. Its keys are column names, and its values are SQL
 
 169 strings by which to sort. Example:
 
 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)',
 
 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.
 
 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.
 
 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.
 
 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
 
 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
 
 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>.
 
 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.
 
 209   nulls => { transaction_description => 'FIRST',
 
 210              customer_name           => 'FIRST',
 
 224 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>