search: add way to search array attributes by regex.

Add a `where_any_in_array_matches_regex` method and expose it to the API:

 * https://danbooru.donmai.us/artists?search[any_other_name_matches_regex]=^blah
 * https://danbooru.donmai.us/wiki_pages?search[any_other_name_matches_regex]=^blah
 * https://danbooru.donmai.us/saved_searches?search[any_label_matches_regex]=^blah

In SQL, this does `WHERE '^blah' ~<< ANY(other_names)`, where `~<<` is a
custom operator based on the `~` regex match operator, but with the
arguments reversed. This allows it to be used with the ANY(array) operator.

See also:

* https://stackoverflow.com/a/22101172
* https://www.postgresql.org/docs/current/sql-createfunction.html
* https://www.postgresql.org/docs/current/sql-createoperator.html
* https://www.postgresql.org/docs/current/functions-comparisons.html
This commit is contained in:
evazion
2021-01-09 20:19:49 -06:00
parent 65adcd09c2
commit 9759701071
4 changed files with 46 additions and 3 deletions

View File

@@ -92,6 +92,11 @@ module Searchable
where("lower(#{qualified_column_for(attr)}::text)::text[] @> ARRAY[?]", values.map(&:downcase))
end
# `~<<` is a custom Postgres operator. It's the `~` regex operator with reversed arguments.
def where_any_in_array_matches_regex(attr, regex, flags: "e")
where("? ~<< ANY(#{qualified_column_for(attr)})", "(?#{flags})#{regex}")
end
def where_text_includes_lower(attr, values)
where("lower(#{qualified_column_for(attr)}) IN (?)", values.map(&:downcase))
end
@@ -340,6 +345,7 @@ module Searchable
def search_array_attribute(name, type, params)
relation = all
singular_name = name.to_s.singularize
if params[:"#{name}_include_any"]
items = params[:"#{name}_include_any"].to_s.scan(/[^[:space:]]+/)
@@ -369,10 +375,12 @@ module Searchable
relation = relation.where_array_includes_any_lower(name, params[:"#{name}_include_any_lower_array"])
elsif params[:"#{name}_include_all_lower_array"]
relation = relation.where_array_includes_all_lower(name, params[:"#{name}_include_all_lower_array"])
elsif params[:"any_#{singular_name}_matches_regex"]
relation = relation.where_any_in_array_matches_regex(name, params[:"any_#{singular_name}_matches_regex"])
end
if params[:"#{name.to_s.singularize}_count"]
relation = relation.where_array_count(name, params[:"#{name.to_s.singularize}_count"])
if params[:"#{singular_name}_count"]
relation = relation.where_array_count(name, params[:"#{singular_name}_count"])
end
relation