Class: RESTFramework::Filters::OrderingFilter

Inherits:
BaseFilter
  • Object
show all
Defined in:
lib/rest_framework/filters/ordering_filter.rb

Overview

A filter backend which handles ordering of the recordset.

Instance Method Summary collapse

Methods inherited from BaseFilter

#_polymorphic_columns, _safe_query_value?, #initialize

Constructor Details

This class inherits a constructor from RESTFramework::Filters::BaseFilter

Instance Method Details

#_get_fieldsObject



3
4
5
6
# File 'lib/rest_framework/filters/ordering_filter.rb', line 3

def _get_fields
  # Always return a list of strings; `@controller.readable_columns_or_associations` already does.
  @controller.class.ordering_fields&.map(&:to_s) || @controller.readable_columns_or_associations
end

#_get_orderingObject

Convert the ordering param into an [ordering, references] pair: the ordering config for order/reorder, and the association names that must be joined for it to resolve.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/rest_framework/filters/ordering_filter.rb', line 10

def _get_ordering
  return nil unless param = @controller.class.ordering_query_param.presence

  # Ensure ordering_fields are strings since the split param will be strings.
  fields = self._get_fields
  poly_columns = self._polymorphic_columns(@controller.class.ordering_fields)
  order_string = @controller.request.query_parameters[param]

  # Reject nested-hash inputs like `?ordering[evil]=x` (Rack parses these into
  # a Hash, which can't be split into ordering tokens).
  return nil unless self.class._safe_query_value?(order_string)
  return nil unless order_string.present?

  ordering = {}.with_indifferent_access
  references = []

  order_string = order_string.join(",") if order_string.is_a?(Array)
  order_string.split(",").map(&:strip).each do |field|
    if field[0] == "-"
      column = field[1..-1]
      direction = :desc
    else
      column = field
      direction = :asc
    end

    # A plain, directly-allowlisted field.
    if column.in?(fields)
      ordering[column] = direction
      next
    end

    # A polymorphic association's `<name>.id`/`<name>.type` maps to a backing column on the base
    # table, so it orders directly with no JOIN (unlike other sub-fields).
    if real_column = poly_columns[column]
      ordering[real_column] = direction
      next
    end

    # A dotted `association.sub_field` token. The root must be an allowlisted association field,
    # and the sub-field must be one of that association's allowlisted fields. Otherwise a client
    # could order by (and infer, via an ordering oracle) a column that is never serialized.
    root, sub = column.split(".", 2)
    next unless sub && root.in?(fields)

    cfg = @controller.class.field_configuration[root]
    next unless cfg && sub.in?(cfg[:fields] || [])

    ordering[column] = direction
    references << root.to_sym
  end

  return nil if ordering.empty?

  [ ordering, references ]
end

#filter_data(data) ⇒ Object

Order data according to the request query parameters.



68
69
70
71
72
73
74
75
76
77
# File 'lib/rest_framework/filters/ordering_filter.rb', line 68

def filter_data(data)
  ordering, references = self._get_ordering
  return data unless ordering

  # Join any referenced associations so dotted ordering keys resolve instead of raising.
  data = data.includes(*references).references(*references) if references.present?

  reorder = !@controller.class.ordering_no_reorder
  data.send(reorder ? :reorder : :order, ordering)
end