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:
@@ -92,6 +92,11 @@ module Searchable
|
|||||||
where("lower(#{qualified_column_for(attr)}::text)::text[] @> ARRAY[?]", values.map(&:downcase))
|
where("lower(#{qualified_column_for(attr)}::text)::text[] @> ARRAY[?]", values.map(&:downcase))
|
||||||
end
|
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)
|
def where_text_includes_lower(attr, values)
|
||||||
where("lower(#{qualified_column_for(attr)}) IN (?)", values.map(&:downcase))
|
where("lower(#{qualified_column_for(attr)}) IN (?)", values.map(&:downcase))
|
||||||
end
|
end
|
||||||
@@ -340,6 +345,7 @@ module Searchable
|
|||||||
|
|
||||||
def search_array_attribute(name, type, params)
|
def search_array_attribute(name, type, params)
|
||||||
relation = all
|
relation = all
|
||||||
|
singular_name = name.to_s.singularize
|
||||||
|
|
||||||
if params[:"#{name}_include_any"]
|
if params[:"#{name}_include_any"]
|
||||||
items = params[:"#{name}_include_any"].to_s.scan(/[^[:space:]]+/)
|
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"])
|
relation = relation.where_array_includes_any_lower(name, params[:"#{name}_include_any_lower_array"])
|
||||||
elsif params[:"#{name}_include_all_lower_array"]
|
elsif params[:"#{name}_include_all_lower_array"]
|
||||||
relation = relation.where_array_includes_all_lower(name, 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
|
end
|
||||||
|
|
||||||
if params[:"#{name.to_s.singularize}_count"]
|
if params[:"#{singular_name}_count"]
|
||||||
relation = relation.where_array_count(name, params[:"#{name.to_s.singularize}_count"])
|
relation = relation.where_array_count(name, params[:"#{singular_name}_count"])
|
||||||
end
|
end
|
||||||
|
|
||||||
relation
|
relation
|
||||||
|
|||||||
11
db/migrate/20210110015410_add_reverse_regex_operator.rb
Normal file
11
db/migrate/20210110015410_add_reverse_regex_operator.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class AddReverseRegexOperator < ActiveRecord::Migration[6.1]
|
||||||
|
def up
|
||||||
|
execute "CREATE FUNCTION reverse_textregexeq (text, text) RETURNS boolean LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$ SELECT textregexeq($2, $1); $$"
|
||||||
|
execute "CREATE OPERATOR ~<< (FUNCTION = reverse_textregexeq, leftarg = text, rightarg = text)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
execute "DROP OPERATOR ~<< (text, text)"
|
||||||
|
execute "DROP FUNCTION reverse_textregexeq (text, text)"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -352,6 +352,15 @@ CREATE FUNCTION public.favorites_insert_trigger() RETURNS trigger
|
|||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: reverse_textregexeq(text, text); Type: FUNCTION; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.reverse_textregexeq(text, text) RETURNS boolean
|
||||||
|
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
||||||
|
AS $_$ SELECT textregexeq($2, $1); $_$;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: testprs_end(internal); Type: FUNCTION; Schema: public; Owner: -
|
-- Name: testprs_end(internal); Type: FUNCTION; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -388,6 +397,17 @@ CREATE FUNCTION public.testprs_start(internal, integer) RETURNS internal
|
|||||||
AS '$libdir/test_parser', 'testprs_start';
|
AS '$libdir/test_parser', 'testprs_start';
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: ~<<; Type: OPERATOR; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE OPERATOR public.~<< (
|
||||||
|
FUNCTION = public.reverse_textregexeq,
|
||||||
|
LEFTARG = text,
|
||||||
|
RIGHTARG = text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: testparser; Type: TEXT SEARCH PARSER; Schema: public; Owner: -
|
-- Name: testparser; Type: TEXT SEARCH PARSER; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -7849,6 +7869,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20210106212805'),
|
('20210106212805'),
|
||||||
('20210108030722'),
|
('20210108030722'),
|
||||||
('20210108030723'),
|
('20210108030723'),
|
||||||
('20210108030724');
|
('20210108030724'),
|
||||||
|
('20210110015410');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ class SearchableTest < ActiveSupport::TestCase
|
|||||||
assert_search_equals(@wp, other_names_include_any_lower_array: ["A1", "BLAH"])
|
assert_search_equals(@wp, other_names_include_any_lower_array: ["A1", "BLAH"])
|
||||||
assert_search_equals(@wp, other_names_include_all_lower_array: ["A1", "B2"])
|
assert_search_equals(@wp, other_names_include_all_lower_array: ["A1", "B2"])
|
||||||
|
|
||||||
|
assert_search_equals(@wp, any_other_name_matches_regex: "^a")
|
||||||
|
assert_search_equals(@wp, any_other_name_matches_regex: "[a-z][0-9]")
|
||||||
|
|
||||||
assert_search_equals(@wp, other_name_count: 2)
|
assert_search_equals(@wp, other_name_count: 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user