Merge pull request #3907 from evazion/fix-3900

#3900: Allow to search for urls associated with artists using wildcards
This commit is contained in:
Albert Yi
2018-09-17 12:03:15 -07:00
committed by GitHub
10 changed files with 237 additions and 15 deletions

View File

@@ -1,6 +1,14 @@
class ArtistUrlsController < ApplicationController
respond_to :json
before_action :member_only
respond_to :json, :xml, :html
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
@artist_url = ArtistUrl.find(params[:id])

View File

@@ -5,6 +5,36 @@ class ApplicationRecord < ActiveRecord::Base
concerning :SearchMethods 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)
return all if value.nil?
@@ -55,6 +85,30 @@ class ApplicationRecord < ActiveRecord::Base
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)
if params[:order] == "custom"
parse_ids = Tag.parse_helper(params[:id])

View File

@@ -6,6 +6,9 @@ class ArtistUrl < ApplicationRecord
validate :validate_url_format
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)
url.sub(/^[-]+/, "")
end
@@ -45,6 +48,46 @@ class ArtistUrl < ApplicationRecord
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
case url
when /^-/

View 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 %>

View File

@@ -5,6 +5,7 @@
<%= subnav_link_to "Banned", banned_artists_path %>
<%= subnav_link_to "New", new_artist_path %>
<%= subnav_link_to "Recent changes", artist_versions_path %>
<%= subnav_link_to "URLs", artist_urls_path %>
<% if @artist && !@artist.new_record? %>
<li>|</li>
<%= subnav_link_to "Posts (#{Post.fast_count(@artist.name)})", posts_path(:tags => @artist.name) %>