Merge pull request #3907 from evazion/fix-3900
#3900: Allow to search for urls associated with artists using wildcards
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
class ArtistUrlsController < ApplicationController
|
class ArtistUrlsController < ApplicationController
|
||||||
respond_to :json
|
respond_to :json, :xml, :html
|
||||||
before_action :member_only
|
before_action :member_only, except: [:index]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@artist_urls = ArtistUrl.includes(:artist).search(search_params).paginate(params[:page], :limit => params[:limit], :search_count => params[:search])
|
||||||
|
respond_with(@artist_urls) do |format|
|
||||||
|
format.json { render json: @artist_urls.to_json(include: "artist",) }
|
||||||
|
format.xml { render xml: @artist_urls.to_xml(include: "artist", root: "artist-urls") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@artist_url = ArtistUrl.find(params[:id])
|
@artist_url = ArtistUrl.find(params[:id])
|
||||||
|
|||||||
@@ -5,6 +5,36 @@ class ApplicationRecord < ActiveRecord::Base
|
|||||||
|
|
||||||
concerning :SearchMethods do
|
concerning :SearchMethods do
|
||||||
class_methods do
|
class_methods do
|
||||||
|
def qualified_column_for(attr)
|
||||||
|
"#{table_name}.#{column_for_attribute(attr).name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_like(attr, value)
|
||||||
|
where("#{qualified_column_for(attr)} LIKE ? ESCAPE E'\\\\'", value.to_escaped_for_sql_like)
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_not_like(attr, value)
|
||||||
|
where.not("#{qualified_column_for(attr)} LIKE ? ESCAPE E'\\\\'", value.to_escaped_for_sql_like)
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_ilike(attr, value)
|
||||||
|
where("lower(#{qualified_column_for(attr)}) LIKE ? ESCAPE E'\\\\'", value.mb_chars.downcase.to_escaped_for_sql_like)
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_not_ilike(attr, value)
|
||||||
|
where.not("lower(#{qualified_column_for(attr)}) LIKE ? ESCAPE E'\\\\'", value.mb_chars.downcase.to_escaped_for_sql_like)
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://www.postgresql.org/docs/current/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
|
||||||
|
# "(?e)" means force use of ERE syntax; see sections 9.7.3.1 and 9.7.3.4.
|
||||||
|
def where_regex(attr, value)
|
||||||
|
where("#{qualified_column_for(attr)} ~ ?", "(?e)" + value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_not_regex(attr, value)
|
||||||
|
where.not("#{qualified_column_for(attr)} ~ ?", "(?e)" + value)
|
||||||
|
end
|
||||||
|
|
||||||
def attribute_matches(attribute, value, **options)
|
def attribute_matches(attribute, value, **options)
|
||||||
return all if value.nil?
|
return all if value.nil?
|
||||||
|
|
||||||
@@ -55,6 +85,30 @@ class ApplicationRecord < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_text_attribute(attr, params, **options)
|
||||||
|
if params[attr].present?
|
||||||
|
where(attr => params[attr])
|
||||||
|
elsif params[:"#{attr}_eq"].present?
|
||||||
|
where(attr => params[:"#{attr}_eq"])
|
||||||
|
elsif params[:"#{attr}_not_eq"].present?
|
||||||
|
where.not(attr => params[:"#{attr}_not_eq"])
|
||||||
|
elsif params[:"#{attr}_like"].present?
|
||||||
|
where_like(attr, params[:"#{attr}_like"])
|
||||||
|
elsif params[:"#{attr}_ilike"].present?
|
||||||
|
where_ilike(attr, params[:"#{attr}_ilike"])
|
||||||
|
elsif params[:"#{attr}_not_like"].present?
|
||||||
|
where_not_like(attr, params[:"#{attr}_not_like"])
|
||||||
|
elsif params[:"#{attr}_not_ilike"].present?
|
||||||
|
where_not_ilike(attr, params[:"#{attr}_not_ilike"])
|
||||||
|
elsif params[:"#{attr}_regex"].present?
|
||||||
|
where_regex(attr, params[:"#{attr}_regex"])
|
||||||
|
elsif params[:"#{attr}_not_regex"].present?
|
||||||
|
where_not_regex(attr, params[:"#{attr}_not_regex"])
|
||||||
|
else
|
||||||
|
all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def apply_default_order(params)
|
def apply_default_order(params)
|
||||||
if params[:order] == "custom"
|
if params[:order] == "custom"
|
||||||
parse_ids = Tag.parse_helper(params[:id])
|
parse_ids = Tag.parse_helper(params[:id])
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ class ArtistUrl < ApplicationRecord
|
|||||||
validate :validate_url_format
|
validate :validate_url_format
|
||||||
belongs_to :artist, :touch => true
|
belongs_to :artist, :touch => true
|
||||||
|
|
||||||
|
scope :url_matches, ->(url) { url_attribute_matches(:url, url) }
|
||||||
|
scope :normalized_url_matches, ->(url) { url_attribute_matches(:normalized_url, url) }
|
||||||
|
|
||||||
def self.strip_prefixes(url)
|
def self.strip_prefixes(url)
|
||||||
url.sub(/^[-]+/, "")
|
url.sub(/^[-]+/, "")
|
||||||
end
|
end
|
||||||
@@ -45,6 +48,46 @@ class ArtistUrl < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.search(params = {})
|
||||||
|
q = super
|
||||||
|
|
||||||
|
q = q.attribute_matches(:artist_id, params[:artist_id])
|
||||||
|
q = q.attribute_matches(:is_active, params[:is_active])
|
||||||
|
q = q.search_text_attribute(:url, params)
|
||||||
|
q = q.search_text_attribute(:normalized_url, params)
|
||||||
|
|
||||||
|
q = q.artist_matches(params[:artist])
|
||||||
|
q = q.url_matches(params[:url_matches])
|
||||||
|
q = q.normalized_url_matches(params[:normalized_url_matches])
|
||||||
|
|
||||||
|
case params[:order]
|
||||||
|
when /\A(id|artist_id|url|normalized_url|is_active|created_at|updated_at)(?:_(asc|desc))?\z/i
|
||||||
|
dir = $2 || :desc
|
||||||
|
q = q.order($1 => dir).order(id: :desc)
|
||||||
|
else
|
||||||
|
q = q.apply_default_order(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
q
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.artist_matches(params = {})
|
||||||
|
return all if params.blank?
|
||||||
|
where(artist_id: Artist.search(params).reorder(nil))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.url_attribute_matches(attr, url)
|
||||||
|
if url.blank?
|
||||||
|
all
|
||||||
|
elsif url =~ %r!\A/(.*)/\z!
|
||||||
|
where_regex(attr, $1)
|
||||||
|
elsif url.include?("*")
|
||||||
|
where_ilike(attr, url)
|
||||||
|
else
|
||||||
|
where(attr => normalize(url))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_prefix
|
def parse_prefix
|
||||||
case url
|
case url
|
||||||
when /^-/
|
when /^-/
|
||||||
|
|||||||
49
app/views/artist_urls/index.html.erb
Normal file
49
app/views/artist_urls/index.html.erb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<div id="c-artist-urls">
|
||||||
|
<div id="a-index">
|
||||||
|
<%= simple_form_for(:search, url: artist_urls_path, method: :get, defaults: { required: false }, html: { class: "inline-form" }) do |f| %>
|
||||||
|
<%= f.simple_fields_for :artist do |fa| %>
|
||||||
|
<%= fa.input :name, label: "Artist Name", input_html: { value: params.dig(:search, :artist, :name), "data-autocomplete": "artist" } %>
|
||||||
|
<% end %>
|
||||||
|
<%= f.input :url_matches, label: "URL", input_html: { value: params[:search][:url_matches] } %>
|
||||||
|
<%= f.input :normalized_url_matches, label: "Normalized URL", input_html: { value: params[:search][:normalized_url_matches] } %>
|
||||||
|
<%= f.input :is_active, label: "Active?", collection: [["Yes", true], ["No", false]], include_blank: true, selected: params[:search][:is_active] %>
|
||||||
|
<%= f.input :order, collection: [["ID", "id"], ["Created", "created_at"], ["Updated", "updated_at"]], selected: params[:search][:order] %>
|
||||||
|
<%= f.submit "Search" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<table class="striped" width="100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Artist Name</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Normalized URL</th>
|
||||||
|
<th>Active?</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Updated</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @artist_urls.each do |artist_url| %>
|
||||||
|
<tr>
|
||||||
|
<%= tag.td artist_url.id %>
|
||||||
|
<%= tag.td link_to(artist_url.artist.name, artist_url.artist) %>
|
||||||
|
<%= tag.td external_link_to(artist_url.url.to_s) %>
|
||||||
|
<%= tag.td external_link_to(artist_url.normalized_url) %>
|
||||||
|
<%= tag.td artist_url.is_active.to_s %>
|
||||||
|
<%= tag.td artist_url.created_at %>
|
||||||
|
<%= tag.td artist_url.updated_at %>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<%= numbered_paginator(@artist_urls) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render "artists/secondary_links" %>
|
||||||
|
|
||||||
|
<% content_for(:page_title) do %>
|
||||||
|
Artist URLs - <%= Danbooru.config.app_name %>
|
||||||
|
<% end %>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<%= subnav_link_to "Banned", banned_artists_path %>
|
<%= subnav_link_to "Banned", banned_artists_path %>
|
||||||
<%= subnav_link_to "New", new_artist_path %>
|
<%= subnav_link_to "New", new_artist_path %>
|
||||||
<%= subnav_link_to "Recent changes", artist_versions_path %>
|
<%= subnav_link_to "Recent changes", artist_versions_path %>
|
||||||
|
<%= subnav_link_to "URLs", artist_urls_path %>
|
||||||
<% if @artist && !@artist.new_record? %>
|
<% if @artist && !@artist.new_record? %>
|
||||||
<li>|</li>
|
<li>|</li>
|
||||||
<%= subnav_link_to "Posts (#{Post.fast_count(@artist.name)})", posts_path(:tags => @artist.name) %>
|
<%= subnav_link_to "Posts (#{Post.fast_count(@artist.name)})", posts_path(:tags => @artist.name) %>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Rails.application.routes.draw do
|
|||||||
get :banned
|
get :banned
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :artist_urls, only: [:update]
|
resources :artist_urls, only: [:index, :update]
|
||||||
resources :artist_versions, :only => [:index] do
|
resources :artist_versions, :only => [:index] do
|
||||||
collection do
|
collection do
|
||||||
get :search
|
get :search
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
class AddTrigramIndexToArtistUrls < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
change_table :artist_urls do |t|
|
||||||
|
t.remove_index column: :url, name: :index_artist_urls_on_url
|
||||||
|
t.remove_index column: :url, name: :index_artist_urls_on_url_pattern, opclass: :text_pattern_ops
|
||||||
|
t.remove_index column: :normalized_url, name: :index_artist_urls_on_normalized_url
|
||||||
|
|
||||||
|
t.index :url, name: :index_artist_urls_on_url_trgm, using: :gin, opclass: :gin_trgm_ops
|
||||||
|
t.index :normalized_url, name: :index_artist_urls_on_normalized_url_trgm, using: :gin, opclass: :gin_trgm_ops
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4944,13 +4944,6 @@ CREATE INDEX index_artist_commentary_versions_on_updater_ip_addr ON public.artis
|
|||||||
CREATE INDEX index_artist_urls_on_artist_id ON public.artist_urls USING btree (artist_id);
|
CREATE INDEX index_artist_urls_on_artist_id ON public.artist_urls USING btree (artist_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: index_artist_urls_on_normalized_url; Type: INDEX; Schema: public; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE INDEX index_artist_urls_on_normalized_url ON public.artist_urls USING btree (normalized_url);
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_artist_urls_on_normalized_url_pattern; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_artist_urls_on_normalized_url_pattern; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4959,17 +4952,17 @@ CREATE INDEX index_artist_urls_on_normalized_url_pattern ON public.artist_urls U
|
|||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_artist_urls_on_url; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_artist_urls_on_normalized_url_trgm; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_artist_urls_on_url ON public.artist_urls USING btree (url);
|
CREATE INDEX index_artist_urls_on_normalized_url_trgm ON public.artist_urls USING gin (normalized_url public.gin_trgm_ops);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_artist_urls_on_url_pattern; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_artist_urls_on_url_trgm; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_artist_urls_on_url_pattern ON public.artist_urls USING btree (url text_pattern_ops);
|
CREATE INDEX index_artist_urls_on_url_trgm ON public.artist_urls USING gin (url public.gin_trgm_ops);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@@ -7532,6 +7525,8 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20180518175154'),
|
('20180518175154'),
|
||||||
('20180804203201'),
|
('20180804203201'),
|
||||||
('20180816230604'),
|
('20180816230604'),
|
||||||
('20180912185624');
|
('20180912185624'),
|
||||||
|
('20180913184128'),
|
||||||
|
('20180916002448');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
25
test/functional/artist_urls_controller_test.rb
Normal file
25
test/functional/artist_urls_controller_test.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ArtistUrlsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
context "The artist urls controller" do
|
||||||
|
context "index page" do
|
||||||
|
should "render" do
|
||||||
|
get artist_urls_path
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
should "render for a complex search" do
|
||||||
|
@artist = FactoryBot.create(:artist, name: "bkub", url_string: "-http://bkub.com")
|
||||||
|
|
||||||
|
get artist_urls_path(search: {
|
||||||
|
artist: { name: "bkub", },
|
||||||
|
url_matches: "*bkub*",
|
||||||
|
is_active: "false",
|
||||||
|
order: "created_at"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ArtistUrlTest < ActiveSupport::TestCase
|
class ArtistUrlTest < ActiveSupport::TestCase
|
||||||
|
def assert_search_equals(results, conditions)
|
||||||
|
assert_equal(results.map(&:id), subject.search(conditions).map(&:id))
|
||||||
|
end
|
||||||
|
|
||||||
context "An artist url" do
|
context "An artist url" do
|
||||||
setup do
|
setup do
|
||||||
CurrentUser.user = FactoryBot.create(:user)
|
CurrentUser.user = FactoryBot.create(:user)
|
||||||
@@ -165,5 +169,36 @@ class ArtistUrlTest < ActiveSupport::TestCase
|
|||||||
url = FactoryBot.create(:artist_url, url: "https://nijie.info/members.php?id=161703")
|
url = FactoryBot.create(:artist_url, url: "https://nijie.info/members.php?id=161703")
|
||||||
assert_equal("http://nijie.info/members.php?id=161703/", url.normalized_url)
|
assert_equal("http://nijie.info/members.php?id=161703/", url.normalized_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "#search method" do
|
||||||
|
subject { ArtistUrl }
|
||||||
|
|
||||||
|
should "work" do
|
||||||
|
@bkub = FactoryBot.create(:artist, name: "bkub", is_active: true, url_string: "https://bkub.com")
|
||||||
|
@masao = FactoryBot.create(:artist, name: "masao", is_active: false, url_string: "-https://masao.com")
|
||||||
|
@bkub_url = @bkub.urls.first
|
||||||
|
@masao_url = @masao.urls.first
|
||||||
|
|
||||||
|
assert_search_equals([@bkub_url], is_active: true)
|
||||||
|
assert_search_equals([@bkub_url], artist: { name: "bkub" })
|
||||||
|
|
||||||
|
assert_search_equals([@bkub_url], url_matches: "*bkub*")
|
||||||
|
assert_search_equals([@bkub_url], url_matches: "/^https?://bkub\.com$/")
|
||||||
|
|
||||||
|
assert_search_equals([@bkub_url], normalized_url_matches: "*bkub*")
|
||||||
|
assert_search_equals([@bkub_url], normalized_url_matches: "/^https?://bkub\.com/$/")
|
||||||
|
assert_search_equals([@bkub_url], normalized_url_matches: "https://bkub.com")
|
||||||
|
|
||||||
|
assert_search_equals([@bkub_url], url: "https://bkub.com")
|
||||||
|
assert_search_equals([@bkub_url], url_eq: "https://bkub.com")
|
||||||
|
assert_search_equals([@bkub_url], url_not_eq: "https://masao.com")
|
||||||
|
assert_search_equals([@bkub_url], url_like: "*bkub*")
|
||||||
|
assert_search_equals([@bkub_url], url_ilike: "*BKUB*")
|
||||||
|
assert_search_equals([@bkub_url], url_not_like: "*masao*")
|
||||||
|
assert_search_equals([@bkub_url], url_not_ilike: "*MASAO*")
|
||||||
|
assert_search_equals([@bkub_url], url_regex: "bkub")
|
||||||
|
assert_search_equals([@bkub_url], url_not_regex: "masao")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user