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';
 
  32   push @$sort_by_str, @{ $sort_spec->{tiebreaker} };
 
  34   $sort_by_str    = join(', ', map { "${_} ${sort_dir_str}${nulls_str}" } @{ $sort_by_str });
 
  36   return wantarray ? ($sort_by, $sort_dir, $sort_by_str) : $sort_by_str;
 
  40   my ($class, %params) = @_;
 
  41   my $sort_str         = $class->make_sort_string(sort_by => delete($params{sort_by}), sort_dir => delete($params{sort_dir}));
 
  43   return $class->get_all(sort_by => $sort_str, %params);
 
  48   return $sort_spec{$class} ||= _make_sort_spec($class);
 
  54   my %sort_spec = defined &{ "${class}::_sort_spec" } ? $class->_sort_spec : ();
 
  56   my $meta = $class->object_class->meta;
 
  57   my $table = $meta->table;
 
  59   if (!$sort_spec{default}) {
 
  60     my @primary_keys = $meta->primary_key;
 
  61     $sort_spec{default} = [ "" . $primary_keys[0], 1 ];
 
  64   $sort_spec{columns} ||= { SIMPLE => [ map { "$_" } $meta->columns ] };
 
  66   if ($sort_spec{columns}->{SIMPLE}) {
 
  67     if (!ref($sort_spec{columns}->{SIMPLE}) && ($sort_spec{columns}->{SIMPLE} eq 'ALL')) {
 
  68       map { $sort_spec{columns}->{"$_"} ||= "${table}.${_}"} @{ $meta->columns };
 
  69       delete $sort_spec{columns}->{SIMPLE};
 
  71       map { $sort_spec{columns}->{$_} = "${table}.${_}" } @{ delete($sort_spec{columns}->{SIMPLE}) };
 
  75   $sort_spec{tiebreaker} ||= [ map { "${table}.${_}" } $meta->primary_key ];
 
  88 SL::DB::Helper::Sorted - Mixin for a manager class that handles
 
  89 sorting of database records
 
  93   package SL::DB::Manager::Message;
 
  95   use SL::DB::Helper::Sorted;
 
  98     return ( columns => { recipient_id => [ 'CASE
 
  99                                              WHEN recipient_group_id IS NULL THEN lower(recipient.name)
 
 100                                              ELSE                                 lower(recipient_group.name)
 
 102                           sender_id    => [ 'lower(sender.name)',                       ],
 
 103                           created_at   => [ 'created_at',                               ],
 
 104                           subject      => [ 'lower(subject)',                           ],
 
 105                           status       => [ 'NOT COALESCE(unread, FALSE)', 'created_at' ],
 
 107              default => [ 'status', 1 ],
 
 108              nulls   => { default => 'LAST',
 
 114   package SL::Controller::Message;
 
 117     my $messages = SL::DB::Manager::Message->get_all_sorted(sort_by  => $::form->{sort_by},
 
 118                                                             sort_dir => $::form->{sort_dir});
 
 121 =head1 CLASS FUNCTIONS
 
 125 =item C<make_sort_string %params>
 
 127 Evaluates C<$params{sort_by}> and C<$params{sort_dir}> and returns an
 
 128 SQL string suitable for sorting. The package this package is mixed
 
 129 into has to provide a method L</_sort_spec> that returns a hash whose
 
 130 structure is explained below. That hash is authoritative in which
 
 131 columns may be sorted, which column to sort by by default and how to
 
 132 handle C<NULL> values.
 
 134 Returns the SQL string in scalar context. In array context it returns
 
 135 three values: the actual column it sorts by (suitable for another call
 
 136 to L</make_sort_string>), the actual sort direction (either 0 or 1)
 
 139 =item C<get_all_sorted %params>
 
 141 Returns C<< $class->get_all >> with C<sort_by> set to the value
 
 142 returned by c<< $class->make_sort_string(%params) >>.
 
 146 =head1 CLASS FUNCTIONS PROVIDED BY THE MIXING PACKAGE
 
 152 This method is actually not part of this package but can be provided
 
 153 by the package this helper is mixed into. If it isn't then all columns
 
 154 of the corresponding table (as returned by the model's meta data) will
 
 155 be eligible for sorting.
 
 157 Returns a hash with the following keys:
 
 163 A two-element array containing the name and direction by which to sort
 
 164 in default cases. Example:
 
 166   default => [ 'name', 1 ],
 
 168 Defaults to the table's primary key column (the first column if the
 
 169 primary key is composited).
 
 173 A hash reference. Its keys are column names, and its values are SQL
 
 174 strings by which to sort. Example:
 
 176   columns => { SIMPLE                  => [ 'transaction_description', 'orddate' ],
 
 177                the_date                => 'CASE WHEN oe.quotation THEN oe.quodate ELSE oe.orddate END',
 
 178                customer_name           => 'lower(customer.name)',
 
 181 If sorting is requested for a column that is not a key in this hash
 
 182 then the default column name will be used.
 
 184 The value can be either a scalar or an array reference. If it's the
 
 185 latter then both the sort direction as well as the null handling will
 
 186 be appended to each of its members.
 
 188 The special key C<SIMPLE> can be a scalar or an array reference. If it
 
 189 is an array reference then it contains column names that are mapped
 
 190 1:1 onto the table's columns. If it is the scalar 'ALL' then all
 
 191 columns in that model's meta data are mapped 1:1 unless the C<columns>
 
 192 hash already contains a key for that column.
 
 194 If C<columns> is missing then all columns of the model will be
 
 195 eligible for sorting. The list of columns is looked up in the model's
 
 200 Either a scalar or a hash reference determining where C<NULL> values
 
 201 will be sorted. If undefined then the decision is left to the
 
 204 If it is a scalar then the same value will be used for all
 
 205 classes. The value is either C<FIRST> or C<LAST>.
 
 207 If it is a hash reference then its keys are column names (not SQL
 
 208 names). The values are either C<FIRST> or C<LAST>. If a column name is
 
 209 not found in this hash then the special key C<default> will be looked
 
 210 up and used if it is found.
 
 214   nulls => { transaction_description => 'FIRST',
 
 215              customer_name           => 'FIRST',
 
 221 Optional tiebreaker sorting that gets appended to any user requested sorting.
 
 222 Needed to make sorting by non unique columns deterministic.
 
 224 If present must be an arrayref of column sort specs (see C<column>).
 
 226 Defaults to primary keys.
 
 238 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>