Skip to content

Commit eadfb91

Browse files
authored
Allow escaped string filters (#102)
Let's say you have a `keywords` filter where users may put commas in the input. Prior to this commit, we would interpret that comma as a delimiter and treat this incorrectly as an array of multiple values. You can now escape values to ensure the desired behavior: ``` ?filter[keywords]={{foo,bar}} ``` Would end up as `.where(keywords: "foo,bar")` This works with arrays: ``` ?filter[keywords]={{foo,bar}},baz,{{another,thing}} ``` Would end up as `.where(keywords: ["foo,bar", "baz", "another,thing"])` Originally, quoting the string was considered. However, for this use case it would be common for users to type quotes, and then you'd have to escape the quotes. Using double-curlies keeps things simple and without conflict.
1 parent 29c9af2 commit eadfb91

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

lib/jsonapi_compliable/scoping/filter.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,31 @@ def each_filter
5252
filter_param.each_pair do |param_name, param_value|
5353
filter = find_filter!(param_name.to_sym)
5454
value = param_value
55-
value = value.split(',') if value.is_a?(String) && value.include?(',')
55+
value = parse_string_arrays(value)
5656
value = normalize_string_values(value)
5757
yield filter, value
5858
end
5959
end
6060

61+
# foo,bar,baz becomes ["foo", "bar", "baz"]
62+
# {{foo,bar}},baz becomes ["foo,bar", "baz"]
63+
def parse_string_arrays(value)
64+
if value.is_a?(String) && value.include?(',')
65+
# Fine the quoted strings
66+
quotes = value.scan(/{{.*?}}/)
67+
# remove them from the rest
68+
quotes.each { |q| value.gsub!(q, '') }
69+
# remove the quote characters from the quoted strings
70+
quotes.each { |q| q.gsub!('{{', '').gsub!('}}', '') }
71+
# merge everything back together into an array
72+
value = value.split(',') + quotes
73+
# remove any blanks that are left
74+
value.reject! { |v| v.length.zero? }
75+
value = value[0] if value.length == 1
76+
end
77+
value
78+
end
79+
6180
# Convert a string of "true" to true, etc
6281
#
6382
# NB - avoid Array(value) here since we might want to

spec/filtering_spec.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,51 @@
8787
end
8888
end
8989

90+
context 'when filter is a {{string}} with a comma' do
91+
before do
92+
params[:filter] = { first_name: '{{foo,bar}}' }
93+
author2.update_attribute(:first_name, 'foo,bar')
94+
end
95+
96+
# todo test dont convert to single el array
97+
it 'does not convert to array' do
98+
ids = scope.resolve.map(&:id)
99+
expect(ids).to eq([author2.id])
100+
end
101+
102+
it 'yields single element, not array' do
103+
query = Author.all
104+
expect(query).to receive(:where)
105+
.with(first_name: "foo,bar").and_call_original
106+
allow(Author).to receive(:all) { query }
107+
scope.resolve
108+
end
109+
110+
context 'when an array of escaped/non-escapred strings' do
111+
before do
112+
params[:filter] = { first_name: '{{foo,bar}},Stephen,{{Harold}}' }
113+
end
114+
115+
it 'works correctly' do
116+
ids = scope.resolve.map(&:id)
117+
expect(ids).to eq([author1.id, author2.id, author4.id])
118+
end
119+
end
120+
121+
context 'when an escaped string contains quoted strings' do
122+
before do
123+
params[:filter] = { first_name: '{{foo "bar"}},baz' }
124+
author2.update_attribute(:first_name, 'foo "bar"')
125+
author3.update_attribute(:first_name, 'baz')
126+
end
127+
128+
it 'works correctly' do
129+
ids = scope.resolve.map(&:id)
130+
expect(ids).to eq([author2.id, author3.id])
131+
end
132+
end
133+
end
134+
90135
context 'when filter is an integer' do
91136
before do
92137
params[:filter] = { id: author1.id }

0 commit comments

Comments
 (0)