• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gregschmit / rails-rest-framework / 24615084524

18 Apr 2026 10:14PM UTC coverage: 87.679% (-0.5%) from 88.164%
24615084524

push

github

gregschmit
Fix rubocop and tests.

2 of 2 new or added lines in 1 file covered. (100.0%)

74 existing lines in 7 files now uncovered.

1103 of 1258 relevant lines covered (87.68%)

205.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.85
/lib/rest_framework/filters/query_filter.rb
1
# A simple filtering backend that supports filtering a recordset based on query parameters.
2
class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
2✔
3
  # Wrapper to indicate a type of query that must be negated with `where.not(...)`.
4
  class Not
2✔
5
    attr_reader :q
2✔
6

7
    def initialize(q)
2✔
8
      @q = q
4✔
9
    end
10
  end
11

12
  PREDICATES = {
13
    true: true,
2✔
14
    false: false,
15
    null: nil,
16
    lt: ->(f, v) { { f => ...v } },
2✔
17
    # `gt` must negate `lte` because Rails doesn't support `>` with endless ranges.
18
    # Ref: https://github.com/rails/rails/pull/47345
19
    gt: ->(f, v) { Not.new({ f => ..v }) },
2✔
20
    lte: ->(f, v) { { f => ..v } },
2✔
21
    gte: ->(f, v) { { f => v.. } },
2✔
22
    not: ->(f, v) { Not.new({ f => v }) },
2✔
23
    cont: ->(f, v) {
24
      [
25
        "#{ActiveRecord::Base.connection.quote_column_name(f)} LIKE ?", "%#{
2✔
26
          ActiveRecord::Base.sanitize_sql_like(v)
27
        }%"
28
      ]
29
    },
30
    in: ->(f, v) {
31
      if v.is_a?(Array)
6✔
32
        { f => v.map { |el| el == "null" ? nil : el } }
12✔
33
      elsif v.is_a?(String)
4✔
34
        { f => v.split(",").map { |el| el == "null" ? nil : el } }
18✔
35
      end
36
    },
37
  }.freeze
38
  PREDICATES_REGEX = /^(.*)_(#{PREDICATES.keys.join("|")})$/
2✔
39

40
  # Get a list of filter fields for the current action.
41
  def _get_fields
2✔
42
    # Always return a list of strings; `@controller.get_fields` already does this.
43
    @controller.class.filter_fields&.map(&:to_s) || @controller.get_fields
176✔
44
  end
45

46
  # Helper to find a variation of a field using a predicate. For example, there could be a field
47
  # called `age`, and if `age_lt` it passed, we should return `["age", "lt"]`. Otherwise, if
48
  # something like `age` is passed, then we should return `["age", nil]`.
49
  def parse_predicate(field)
2✔
50
    if match = PREDICATES_REGEX.match(field)
80✔
51
      field = match[1]
18✔
52
      predicate = match[2]
18✔
53
    end
54

55
    return field, predicate
80✔
56
  end
57

58
  # Filter params for keys allowed by the current action's filter_fields/fields config and return a
59
  # query config in the form of: `[base_query, pred_queries, includes]`.
60
  def _get_query_config
2✔
61
    fields = self._get_fields
176✔
62
    includes = []
176✔
63

64
    # Predicate queries must be added to a separate list because multiple predicates can be used.
65
    # E.g., `age_lt=10&age_gte=5` would transform to `[{age: ...10}, {age: 5..}]` to avoid conflict
66
    # on the `age` key.
67
    pred_queries = []
176✔
68

69
    base_query = @controller.request.query_parameters.map { |field, v|
176✔
70
      # First, if field is a simple filterable field, return early.
71
      if field.in?(fields)
88✔
72
        next [ field, v ]
8✔
73
      end
74

75
      # First, try to parse a simple predicate and check if it is filterable.
76
      pred_field, predicate = self.parse_predicate(field)
80✔
77
      if predicate && pred_field.in?(fields)
80✔
78
        field = pred_field
16✔
79
      else
80
        # Last, try to parse a sub-field or sub-field w/predicate.
81
        root_field, sub_field = field.split(".", 2)
64✔
82
        _, pred_sub_field = pred_field.split(".", 2) if predicate
64✔
83

84
        # Check if sub-field or sub-field w/predicate is filterable.
85
        if sub_field
64✔
86
          next nil unless root_field.in?(fields)
2✔
87

88
          sub_fields = @controller.class.field_configuration[root_field][:sub_fields] || []
2✔
89
          if sub_field.in?(sub_fields)
2✔
90
            includes << root_field.to_sym
×
UNCOV
91
            next [ field, v ]
×
92
          elsif pred_sub_field && pred_sub_field.in?(sub_fields)
2✔
93
            includes << root_field.to_sym
2✔
94
            field = pred_field
2✔
95
          else
UNCOV
96
            next nil
×
97
          end
98
        else
99
          next nil
62✔
100
        end
101
      end
102

103
      # If we get here, we must have a predicate, either from a field or a sub-field. Transform the
104
      # value into a query that can be used in the ActiveRecord `where` API.
105
      cfg = PREDICATES[predicate.to_sym]
18✔
106
      if cfg.is_a?(Proc)
18✔
107
        pred_queries << cfg.call(field, v)
18✔
108
      else
UNCOV
109
        pred_queries << { field => cfg }
×
110
      end
111

112
      next nil
18✔
113
    }.compact.to_h.symbolize_keys
114

115
    return base_query, pred_queries, includes
176✔
116
  end
117

118
  # Filter data according to the request query parameters.
119
  def filter_data(data)
2✔
120
    base_query, pred_queries, includes = self._get_query_config
176✔
121

122
    if base_query.any? || pred_queries.any?
176✔
123
      if includes.any?
26✔
124
        data = data.includes(*includes)
2✔
125
      end
126

127
      data = data.where(**base_query) if base_query.any?
26✔
128

129
      pred_queries.each do |q|
26✔
130
        if q.is_a?(Not)
18✔
131
          data = data.where.not(q.q)
4✔
132
        else
133
          data = data.where(q)
14✔
134
        end
135
      end
136
    end
137

138
    data
176✔
139
  end
140
end
141

142
# Alias for convenience.
143
RESTFramework::QueryFilter = RESTFramework::Filters::QueryFilter
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc