Merge pull request #3989 from evazion/fix-3987
Wiki pages: convert other_names column to array (#3987)
This commit is contained in:
@@ -269,6 +269,37 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
concerning :AttributeMethods do
|
||||
class_methods do
|
||||
# Defines `<attribute>_string`, `<attribute>_string=`, and `<attribute>=`
|
||||
# methods for converting an array attribute to or from a string.
|
||||
#
|
||||
# The `<attribute>=` setter parses strings into an array using the
|
||||
# `parse` regex. The resulting strings can be converted to another type
|
||||
# with the `cast` option.
|
||||
def array_attribute(name, parse: /[^[:space:]]+/, cast: :itself)
|
||||
define_method "#{name}_string" do
|
||||
send(name).join(" ")
|
||||
end
|
||||
|
||||
define_method "#{name}_string=" do |value|
|
||||
raise ArgumentError, "#{name} must be a String" unless value.respond_to?(:to_str)
|
||||
send("#{name}=", value)
|
||||
end
|
||||
|
||||
define_method "#{name}=" do |value|
|
||||
if value.respond_to?(:to_str)
|
||||
super value.to_str.scan(parse).map(&cast)
|
||||
elsif value.respond_to?(:to_a)
|
||||
super value.to_a
|
||||
else
|
||||
raise ArgumentError, "#{name} must be a String or an Array"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def warnings
|
||||
@warnings ||= ActiveModel::Errors.new(self)
|
||||
end
|
||||
|
||||
@@ -3,8 +3,10 @@ class Artist < ApplicationRecord
|
||||
class RevertError < Exception ; end
|
||||
|
||||
attr_accessor :url_string_changed
|
||||
array_attribute :other_names
|
||||
|
||||
before_validation :normalize_name
|
||||
before_validation :normalize_other_names
|
||||
after_save :create_version
|
||||
after_save :categorize_tag
|
||||
after_save :update_wiki
|
||||
@@ -174,11 +176,11 @@ class Artist < ApplicationRecord
|
||||
end
|
||||
|
||||
def url_array
|
||||
urls.map(&:to_s)
|
||||
urls.map(&:to_s).sort
|
||||
end
|
||||
|
||||
def url_string
|
||||
url_array.sort.join("\n")
|
||||
url_array.join("\n")
|
||||
end
|
||||
|
||||
def url_string=(string)
|
||||
@@ -239,16 +241,8 @@ class Artist < ApplicationRecord
|
||||
name.tr("_", " ")
|
||||
end
|
||||
|
||||
def other_names_array
|
||||
other_names.try(:split, /[[:space:]]+/)
|
||||
end
|
||||
|
||||
def other_names_comma
|
||||
other_names_array.try(:join, ", ")
|
||||
end
|
||||
|
||||
def other_names_comma=(string)
|
||||
self.other_names = string.split(/,/).map {|x| Artist.normalize_name(x)}.join(" ")
|
||||
def normalize_other_names
|
||||
self.other_names = other_names.map { |x| Artist.normalize_name(x) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -275,7 +269,7 @@ class Artist < ApplicationRecord
|
||||
:name => name,
|
||||
:updater_id => CurrentUser.id,
|
||||
:updater_ip_addr => CurrentUser.ip_addr,
|
||||
:url_string => url_string,
|
||||
:urls => url_array,
|
||||
:is_active => is_active,
|
||||
:is_banned => is_banned,
|
||||
:other_names => other_names,
|
||||
@@ -287,7 +281,7 @@ class Artist < ApplicationRecord
|
||||
prev = versions.last
|
||||
prev.update_attributes(
|
||||
:name => name,
|
||||
:url_string => url_string,
|
||||
:urls => url_array,
|
||||
:is_active => is_active,
|
||||
:is_banned => is_banned,
|
||||
:other_names => other_names,
|
||||
@@ -306,7 +300,7 @@ class Artist < ApplicationRecord
|
||||
end
|
||||
|
||||
self.name = version.name
|
||||
self.url_string = version.url_string
|
||||
self.url_string = version.urls.join("\n")
|
||||
self.is_active = version.is_active
|
||||
self.other_names = version.other_names
|
||||
self.group_name = version.group_name
|
||||
@@ -466,13 +460,21 @@ class Artist < ApplicationRecord
|
||||
where(name: normalize_name(name))
|
||||
end
|
||||
|
||||
def any_other_name_matches(regex)
|
||||
where(id: Artist.from("unnest(other_names) AS other_name").where("other_name ~ ?", regex))
|
||||
end
|
||||
|
||||
def any_other_name_like(name)
|
||||
where(id: Artist.from("unnest(other_names) AS other_name").where("other_name LIKE ?", name.to_escaped_for_sql_like))
|
||||
end
|
||||
|
||||
def any_name_matches(query)
|
||||
if query =~ %r!\A/(.*)/\z!
|
||||
where_regex(:name, $1).or(where_regex(:other_names, $1)).or(where_regex(:group_name, $1))
|
||||
where_regex(:name, $1).or(any_other_name_matches($1)).or(where_regex(:group_name, $1))
|
||||
else
|
||||
normalized_name = normalize_name(query)
|
||||
normalized_name = "*#{normalized_name}*" unless normalized_name.include?("*")
|
||||
where_like(:name, normalized_name).or(where_like(:other_names, normalized_name)).or(where_like(:group_name, normalized_name))
|
||||
where_like(:name, normalized_name).or(any_other_name_like(normalized_name)).or(where_like(:group_name, normalized_name))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -492,9 +494,12 @@ class Artist < ApplicationRecord
|
||||
q = super
|
||||
|
||||
q = q.search_text_attribute(:name, params)
|
||||
q = q.search_text_attribute(:other_names, params)
|
||||
q = q.search_text_attribute(:group_name, params)
|
||||
|
||||
if params[:any_other_name_like]
|
||||
q = q.any_other_name_like(params[:any_other_name_like])
|
||||
end
|
||||
|
||||
if params[:any_name_matches].present?
|
||||
q = q.any_name_matches(params[:any_name_matches])
|
||||
end
|
||||
@@ -536,12 +541,6 @@ class Artist < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
module ApiMethods
|
||||
def hidden_attributes
|
||||
super + [:other_names_index]
|
||||
end
|
||||
end
|
||||
|
||||
include UrlMethods
|
||||
include NameMethods
|
||||
include GroupMethods
|
||||
@@ -551,7 +550,6 @@ class Artist < ApplicationRecord
|
||||
include TagMethods
|
||||
include BanMethods
|
||||
extend SearchMethods
|
||||
include ApiMethods
|
||||
|
||||
def status
|
||||
if is_banned? && is_active?
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class ArtistVersion < ApplicationRecord
|
||||
array_attribute :urls
|
||||
array_attribute :other_names
|
||||
|
||||
belongs_to_updater
|
||||
belongs_to :artist
|
||||
delegate :visible?, :to => :artist
|
||||
@@ -47,18 +50,10 @@ class ArtistVersion < ApplicationRecord
|
||||
|
||||
extend SearchMethods
|
||||
|
||||
def url_array
|
||||
url_string.to_s.split(/[[:space:]]+/)
|
||||
end
|
||||
|
||||
def other_names_array
|
||||
other_names.to_s.split(/[[:space:]]+/)
|
||||
end
|
||||
|
||||
def urls_diff(version)
|
||||
latest_urls = artist.url_array || []
|
||||
new_urls = url_array
|
||||
old_urls = version.present? ? version.url_array : []
|
||||
new_urls = urls
|
||||
old_urls = version.present? ? version.urls : []
|
||||
|
||||
added_urls = new_urls - old_urls
|
||||
removed_urls = old_urls - new_urls
|
||||
@@ -73,9 +68,9 @@ class ArtistVersion < ApplicationRecord
|
||||
end
|
||||
|
||||
def other_names_diff(version)
|
||||
latest_names = artist.other_names_array || []
|
||||
new_names = other_names_array
|
||||
old_names = version.present? ? version.other_names_array : []
|
||||
latest_names = artist.other_names || []
|
||||
new_names = other_names
|
||||
old_names = version.present? ? version.other_names : []
|
||||
|
||||
added_names = new_names - old_names
|
||||
removed_names = old_names - new_names
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
class Pool < ApplicationRecord
|
||||
class RevertError < Exception ; end
|
||||
|
||||
array_attribute :post_ids, parse: /\d+/, cast: :to_i
|
||||
belongs_to_creator
|
||||
|
||||
validates_uniqueness_of :name, case_sensitive: false, if: :name_changed?
|
||||
validate :validate_name, if: :name_changed?
|
||||
validates_inclusion_of :category, :in => %w(series collection)
|
||||
validate :updater_can_change_category
|
||||
validate :updater_can_remove_posts
|
||||
validate :updater_can_edit_deleted
|
||||
belongs_to_creator
|
||||
before_validation :normalize_post_ids
|
||||
before_validation :normalize_name
|
||||
after_save :update_category_pseudo_tags_for_posts_async
|
||||
@@ -153,18 +155,6 @@ class Pool < ApplicationRecord
|
||||
self.post_ids = post_ids.uniq if is_collection?
|
||||
end
|
||||
|
||||
# allow assigning a string to post_ids so it can be assigned from the text
|
||||
# field in the pool edit form (PUT /pools/1?post_ids=1+2+3).
|
||||
def post_ids=(value)
|
||||
if value.respond_to?(:to_str)
|
||||
super value.to_str.scan(/\d+/).map(&:to_i)
|
||||
elsif value.respond_to?(:to_a)
|
||||
super value.to_a
|
||||
else
|
||||
raise ArgumentError, "post_ids must be a String or an Array"
|
||||
end
|
||||
end
|
||||
|
||||
def revert_to!(version)
|
||||
if id != version.pool_id
|
||||
raise RevertError.new("You cannot revert to a previous version of another pool.")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class TagImplication < TagRelationship
|
||||
extend Memoist
|
||||
|
||||
array_attribute :descendant_names
|
||||
|
||||
before_save :update_descendant_names
|
||||
after_save :update_descendant_names_for_parents
|
||||
after_destroy :update_descendant_names_for_parents
|
||||
@@ -22,7 +24,7 @@ class TagImplication < TagRelationship
|
||||
module ClassMethods
|
||||
# assumes names are normalized
|
||||
def with_descendants(names)
|
||||
(names + active.where(antecedent_name: names).flat_map(&:descendant_names_array)).uniq
|
||||
(names + active.where(antecedent_name: names).flat_map(&:descendant_names)).uniq
|
||||
end
|
||||
|
||||
def automatic_tags_for(names)
|
||||
@@ -44,12 +46,8 @@ class TagImplication < TagRelationship
|
||||
end
|
||||
memoize :descendants
|
||||
|
||||
def descendant_names_array
|
||||
descendant_names.split(/ /)
|
||||
end
|
||||
|
||||
def update_descendant_names
|
||||
self.descendant_names = descendants.join(" ")
|
||||
self.descendant_names = descendants
|
||||
end
|
||||
|
||||
def update_descendant_names!
|
||||
@@ -88,7 +86,7 @@ class TagImplication < TagRelationship
|
||||
def absence_of_transitive_relation
|
||||
# Find everything else the antecedent implies, not including the current implication.
|
||||
implications = TagImplication.active.where("antecedent_name = ? and consequent_name != ?", antecedent_name, consequent_name)
|
||||
implied_tags = implications.flat_map(&:descendant_names_array)
|
||||
implied_tags = implications.flat_map(&:descendant_names)
|
||||
if implied_tags.include?(consequent_name)
|
||||
self.errors[:base] << "#{antecedent_name} already implies #{consequent_name} through another implication"
|
||||
end
|
||||
@@ -170,7 +168,7 @@ class TagImplication < TagRelationship
|
||||
def update_posts
|
||||
Post.without_timeout do
|
||||
Post.raw_tag_match(antecedent_name).where("true /* TagImplication#update_posts */").find_each do |post|
|
||||
fixed_tags = "#{post.tag_string} #{descendant_names}".strip
|
||||
fixed_tags = "#{post.tag_string} #{descendant_names_string}".strip
|
||||
CurrentUser.scoped(creator, creator_ip_addr) do
|
||||
post.update_attributes(
|
||||
:tag_string => fixed_tags
|
||||
|
||||
@@ -4,14 +4,16 @@ class WikiPage < ApplicationRecord
|
||||
before_save :normalize_title
|
||||
before_save :normalize_other_names
|
||||
after_save :create_version
|
||||
belongs_to_creator
|
||||
belongs_to_updater
|
||||
validates_uniqueness_of :title, :case_sensitive => false
|
||||
validates_presence_of :title
|
||||
validates_presence_of :body, :unless => -> { is_deleted? || other_names.present? }
|
||||
validate :validate_rename
|
||||
validate :validate_not_locked
|
||||
|
||||
attr_accessor :skip_secondary_validations
|
||||
array_attribute :other_names
|
||||
belongs_to_creator
|
||||
belongs_to_updater
|
||||
has_one :tag, :foreign_key => "name", :primary_key => "title"
|
||||
has_one :artist, -> {where(:is_active => true)}, :foreign_key => "name", :primary_key => "title"
|
||||
has_many :versions, -> {order("wiki_page_versions.id ASC")}, :class_name => "WikiPageVersion", :dependent => :destroy
|
||||
@@ -29,17 +31,16 @@ class WikiPage < ApplicationRecord
|
||||
order("updated_at DESC").limit(25)
|
||||
end
|
||||
|
||||
def other_names_equal(name)
|
||||
query_sql = name.unicode_normalize(:nfkc).to_escaped_for_tsquery
|
||||
where("other_names_index @@ to_tsquery('danbooru', E?)", query_sql)
|
||||
def other_names_include(name)
|
||||
where("wiki_pages.other_names @> ARRAY[?]", name.unicode_normalize(:nfkc))
|
||||
end
|
||||
|
||||
def other_names_match(name)
|
||||
if name =~ /\*/
|
||||
subquery = WikiPage.from("unnest(string_to_array(other_names, ' ')) AS other_name").where("other_name ILIKE ?", name.to_escaped_for_sql_like)
|
||||
subquery = WikiPage.from("unnest(other_names) AS other_name").where("other_name ILIKE ?", name.to_escaped_for_sql_like)
|
||||
where(id: subquery)
|
||||
else
|
||||
other_names_equal(name)
|
||||
other_names_include(name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,9 +74,9 @@ class WikiPage < ApplicationRecord
|
||||
end
|
||||
|
||||
if params[:other_names_present].to_s.truthy?
|
||||
q = q.where("other_names is not null and other_names != ''")
|
||||
q = q.where("other_names is not null and other_names != '{}'")
|
||||
elsif params[:other_names_present].to_s.falsy?
|
||||
q = q.where("other_names is null or other_names = ''")
|
||||
q = q.where("other_names is null or other_names = '{}'")
|
||||
end
|
||||
|
||||
q = q.attribute_matches(:is_locked, params[:is_locked])
|
||||
@@ -97,7 +98,7 @@ class WikiPage < ApplicationRecord
|
||||
|
||||
module ApiMethods
|
||||
def hidden_attributes
|
||||
super + [:body_index, :other_names_index]
|
||||
super + [:body_index]
|
||||
end
|
||||
|
||||
def method_attributes
|
||||
@@ -145,8 +146,7 @@ class WikiPage < ApplicationRecord
|
||||
end
|
||||
|
||||
def normalize_other_names
|
||||
normalized_other_names = other_names.to_s.unicode_normalize(:nfkc).scan(/[^[:space:]]+/)
|
||||
self.other_names = normalized_other_names.uniq.join(" ")
|
||||
self.other_names = other_names.map { |name| name.unicode_normalize(:nfkc) }.uniq
|
||||
end
|
||||
|
||||
def skip_secondary_validations=(value)
|
||||
@@ -224,8 +224,4 @@ class WikiPage < ApplicationRecord
|
||||
def visible?
|
||||
artist.blank? || !artist.is_banned? || CurrentUser.is_builder?
|
||||
end
|
||||
|
||||
def other_names_array
|
||||
other_names.to_s.split(/[[:space:]]+/)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class WikiPageVersion < ApplicationRecord
|
||||
array_attribute :other_names
|
||||
belongs_to :wiki_page
|
||||
belongs_to_updater
|
||||
belongs_to :artist, optional: true
|
||||
@@ -45,8 +46,4 @@ class WikiPageVersion < ApplicationRecord
|
||||
def category_name
|
||||
Tag.category_for(title)
|
||||
end
|
||||
|
||||
def other_names_array
|
||||
other_names.to_s.split(/[[:space:]]+/)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user