-
Notifications
You must be signed in to change notification settings - Fork 57
/
filtering.rb
103 lines (87 loc) · 3.6 KB
/
filtering.rb
1
2
3
4
5
6
7
8
9
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
require 'ransack/predicate'
require_relative 'patches'
# Filtering and sorting support
module JSONAPI
module Filtering
# Parses and returns the attribute and the predicate of a ransack field
#
# @param requested_field [String] the field to parse
# @return [Array] with the fields and the predicate
def self.extract_attributes_and_predicates(requested_field)
predicates = []
field_name = requested_field.to_s.dup
while Ransack::Predicate.detect_from_string(field_name).present? do
predicate = Ransack::Predicate
.detect_and_strip_from_string!(field_name)
predicates << Ransack::Predicate.named(predicate)
end
[field_name.split(/_and_|_or_/), predicates.reverse]
end
private
# Applies filtering and sorting to a set of resources if requested
#
# The fields follow [Ransack] specifications.
# See: https://github.com/activerecord-hackery/ransack#search-matchers
#
# Ex.: `GET /resource?filter[region_matches_any]=Lisb%&sort=-created_at,id`
#
# @param allowed_fields [Array] a list of allowed fields to be filtered
# @param options [Hash] extra flags to enable/disable features
# @return [ActiveRecord::Base] a collection of resources
def jsonapi_filter(resources, allowed_fields, options = {})
allowed_fields = allowed_fields.map(&:to_s)
extracted_params = jsonapi_filter_params(allowed_fields)
extracted_params[:sorts] = jsonapi_sort_params(allowed_fields, options)
resources = resources.ransack(extracted_params)
block_given? ? yield(resources) : resources
end
# Extracts and whitelists allowed fields to be filtered
#
# The fields follow [Ransack] specifications.
# See: https://github.com/activerecord-hackery/ransack#search-matchers
#
# @param allowed_fields [Array] a list of allowed fields to be filtered
# @return [Hash] to be passed to [ActiveRecord::Base#order]
def jsonapi_filter_params(allowed_fields)
filtered = {}
requested = params[:filter] || {}
allowed_fields = allowed_fields.map(&:to_s)
requested.each_pair do |requested_field, to_filter|
field_names, predicates = JSONAPI::Filtering
.extract_attributes_and_predicates(requested_field)
wants_array = predicates.any? && predicates.map(&:wants_array).any?
if to_filter.is_a?(String) && wants_array
to_filter = to_filter.split(',')
end
if predicates.any? && (field_names - allowed_fields).empty?
filtered[requested_field] = to_filter
end
end
filtered
end
# Extracts and whitelists allowed fields (or expressions) to be sorted
#
# @param allowed_fields [Array] a list of allowed fields to be sorted
# @param options [Hash] extra options to enable/disable features
# @return [Hash] to be passed to [ActiveRecord::Base#order]
def jsonapi_sort_params(allowed_fields, options = {})
filtered = []
requested = params[:sort].to_s.split(',')
requested.each do |requested_field|
if requested_field.to_s.start_with?('-')
dir = 'desc'
requested_field = requested_field[1..-1]
else
dir = 'asc'
end
field_names, predicates = JSONAPI::Filtering
.extract_attributes_and_predicates(requested_field)
next unless (field_names - allowed_fields).empty?
next if !options[:sort_with_expressions] && predicates.any?
# Convert to strings instead of hashes to allow joined table columns.
filtered << [requested_field, dir].join(' ')
end
filtered
end
end
end