updated tag unit tests
This commit is contained in:
2
app/models/favorite.rb
Normal file
2
app/models/favorite.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class Favorite < ActiveRecord::Base
|
||||
end
|
||||
26
app/models/pool.rb
Normal file
26
app/models/pool.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class Pool < ActiveRecord::Base
|
||||
validates_uniqueness_of :name
|
||||
validates_presence_of :name
|
||||
validates_format_of :name, :with => /\A[^\s;,]+\Z/, :on => :create, :message => "cannot have whitespace, commas, or semicolons"
|
||||
belongs_to :creator, :class_name => "Person"
|
||||
|
||||
def self.create_anonymous(creator)
|
||||
pool = Pool.create(:name => "TEMP - #{Time.now.to_f}.#{rand(1_000_000)}", :creator => creator)
|
||||
pool.update_attribute(:name => "anonymous:#{pool.id}")
|
||||
pool
|
||||
end
|
||||
|
||||
def neighbor_posts(post)
|
||||
post_ids =~ /\A#{post.id} (\d+)|(\d+) #{post.id} (\d+)|(\d+) #{post.id}\Z/
|
||||
|
||||
if $2 && $3
|
||||
{:previous => $2.to_i, :next = $3.to_i}
|
||||
elsif $1
|
||||
{:previous => $1.to_i}
|
||||
elsif $4
|
||||
{:next => $4.to_i}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,7 @@ class Post < ActiveRecord::Base
|
||||
after_save :create_version
|
||||
before_save :merge_old_tags
|
||||
before_save :normalize_tags
|
||||
before_save :set_tag_counts
|
||||
has_many :versions, :class_name => "PostVersion"
|
||||
|
||||
module FileMethods
|
||||
@@ -133,11 +134,7 @@ class Post < ActiveRecord::Base
|
||||
|
||||
module TagMethods
|
||||
def tag_array(reload = false)
|
||||
if @tag_array.nil? || reload
|
||||
@tag_array = Tag.scan_tags(tag_string)
|
||||
end
|
||||
|
||||
@tag_array
|
||||
Tag.scan_tags(tag_string)
|
||||
end
|
||||
|
||||
def set_tag_counts
|
||||
@@ -171,10 +168,10 @@ class Post < ActiveRecord::Base
|
||||
if old_tag_string
|
||||
# If someone else committed changes to this post before we did,
|
||||
# then try to merge the tag changes together.
|
||||
db_tags = Tag.scan_tags(tag_string_was)
|
||||
current_tags = Tag.scan_tags(tag_string_was)
|
||||
new_tags = tag_array()
|
||||
old_tags = Tag.scan_tags(old_tag_string)
|
||||
self.tag_string = (db_tags + (new_tags - old_tags) - (old_tags - new_tags)).uniq.join(" ")
|
||||
self.tag_string = ((current_tags + new_tags) - old_tags + (current_tags & new_tags)).uniq.join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,87 +179,24 @@ class Post < ActiveRecord::Base
|
||||
normalized_tags = Tag.scan_tags(tag_string)
|
||||
# normalized_tags = TagAlias.to_aliased(normalized_tags)
|
||||
# normalized_tags = TagImplication.with_implications(normalized_tags)
|
||||
normalized_tags = parse_metatags(normalized_tags)
|
||||
normalized_tags = filter_metatags(normalized_tags)
|
||||
self.tag_string = normalized_tags.uniq.join(" ")
|
||||
end
|
||||
|
||||
def parse_metatags(tags)
|
||||
tags.map do |tag|
|
||||
if tag =~ /^(?:pool|rating|fav|user|uploader):(.+)/
|
||||
case $1
|
||||
when "pool"
|
||||
parse_pool_tag($2)
|
||||
|
||||
when "rating"
|
||||
parse_rating_tag($2)
|
||||
|
||||
when "fav"
|
||||
parse_fav_tag($2)
|
||||
|
||||
when "uploader"
|
||||
# ignore
|
||||
|
||||
when "user"
|
||||
# ignore
|
||||
end
|
||||
|
||||
nil
|
||||
else
|
||||
tag
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
def parse_pool_tag(text)
|
||||
case text
|
||||
when "new"
|
||||
pool = Pool.create_anonymous
|
||||
pool.posts << self
|
||||
|
||||
when "recent"
|
||||
raise NotImplementedError
|
||||
|
||||
when /^\d+$/
|
||||
pool = Pool.find_by_id(text.to_i)
|
||||
pool.posts << self if pool
|
||||
|
||||
else
|
||||
pool = Pool.find_by_name(text)
|
||||
pool.posts << self if pool
|
||||
end
|
||||
end
|
||||
|
||||
def parse_rating_tag(rating)
|
||||
case rating
|
||||
when /q/
|
||||
self.rating = "q"
|
||||
|
||||
when /e/
|
||||
self.rating = "e"
|
||||
|
||||
when /s/
|
||||
self.rating = "s"
|
||||
end
|
||||
end
|
||||
|
||||
def parse_fav_tag(text)
|
||||
case text
|
||||
when "add", "new"
|
||||
add_favorite(updater_id)
|
||||
|
||||
when "remove", "rem", "del"
|
||||
remove_favorite(updater_id)
|
||||
end
|
||||
def filter_metatags(tags)
|
||||
tags.reject {|tag| tag =~ /\A(?:pool|rating|fav|approver|uploader):/}
|
||||
end
|
||||
end
|
||||
|
||||
module FavoriteMethods
|
||||
def add_favorite(user_id)
|
||||
self.fav_string += " fav:#{user_id}"
|
||||
def add_favorite(user)
|
||||
self.fav_string += " fav:#{user.name}"
|
||||
self.fav_string.strip!
|
||||
end
|
||||
|
||||
def remove_favorite(user_id)
|
||||
self.fav_string.gsub!(/user:#{user_id}\b\s*/, " ")
|
||||
def remove_favorite(user)
|
||||
self.fav_string.gsub!(/fav:#{user.name}\b\s*/, " ")
|
||||
self.fav_string.strip!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -300,6 +234,48 @@ class Post < ActiveRecord::Base
|
||||
"''" + escaped_token + "''"
|
||||
end
|
||||
end
|
||||
|
||||
def add_tag_string_search_relation(tags, relation)
|
||||
tag_query_sql = []
|
||||
|
||||
if tags[:include].any?
|
||||
tag_query_sql << "(" + escape_string_for_tsquery(tags[:include]).join(" | ") + ")"
|
||||
end
|
||||
|
||||
if tags[:related].any?
|
||||
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if tags[:related].size > Danbooru.config.tag_query_limit
|
||||
tag_query_sql << "(" + escape_string_for_tsquery(tags[:related]).join(" & ") + ")"
|
||||
end
|
||||
|
||||
if tags[:exclude].any?
|
||||
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if tags[:exclude].size > Danbooru.config.tag_query_limit
|
||||
|
||||
if tags[:related].any? || tags[:include].any?
|
||||
tag_query_sql << "!(" + escape_string_for_tsquery(tags[:exclude]).join(" | ") + ")"
|
||||
else
|
||||
raise SearchError.new("You cannot search for only excluded tags")
|
||||
end
|
||||
end
|
||||
|
||||
if tag_query_sql.any?
|
||||
relation.where("posts.tag_index @@ to_tsquery('danbooru', E'" + tag_query_sql.join(" & ") + "')")
|
||||
end
|
||||
end
|
||||
|
||||
def add_tag_subscription_relation(subscriptions, relation)
|
||||
subscriptions.each do |subscription|
|
||||
subscription =~ /^(.+?):(.+)$/
|
||||
user_name = $1 || subscription
|
||||
subscription_name = $2
|
||||
|
||||
user = User.find_by_name(user_name)
|
||||
|
||||
if user
|
||||
post_ids = TagSubscription.find_post_ids(user.id, subscription_name)
|
||||
relation.where(["posts.id IN (?)", post_ids])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_relation(q, options = {})
|
||||
unless q.is_a?(Hash)
|
||||
@@ -326,7 +302,7 @@ class Post < ActiveRecord::Base
|
||||
end
|
||||
|
||||
if q[:md5].is_a?(String)
|
||||
relation.where(["posts.md5 IN (?)", q[:md5].split(/,/)])
|
||||
relation.where(["posts.md5 IN (?)", q[:md5]])
|
||||
end
|
||||
|
||||
if q[:status] == "deleted"
|
||||
@@ -343,45 +319,11 @@ class Post < ActiveRecord::Base
|
||||
relation.where(["posts.source LIKE ? ESCAPE E'\\\\", q[:source]])
|
||||
end
|
||||
|
||||
if q[:subscriptions].is_a?(String)
|
||||
raise NotImplementedError
|
||||
|
||||
q[:subscriptions] =~ /^(.+?):(.+)$/
|
||||
username = $1 || q[:subscriptions]
|
||||
subscription_name = $2
|
||||
|
||||
user = User.find_by_name(username)
|
||||
|
||||
if user
|
||||
post_ids = TagSubscription.find_post_ids(user.id, subscription_name)
|
||||
relation.where(["posts.id IN (?)", post_ids])
|
||||
end
|
||||
if q[:subscriptions].any?
|
||||
add_tag_subscription_relation(q[:subscriptions], relation)
|
||||
end
|
||||
|
||||
tag_query_sql = []
|
||||
|
||||
if q[:include].any?
|
||||
tag_query_sql << "(" + escape_string_for_tsquery(q[:include]).join(" | ") + ")"
|
||||
end
|
||||
|
||||
if q[:related].any?
|
||||
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if q[:related].size > Danbooru.config.tag_query_limit
|
||||
tag_query_sql << "(" + escape_string_for_tsquery(q[:related]).join(" & ") + ")"
|
||||
end
|
||||
|
||||
if q[:exclude].any?
|
||||
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if q[:exclude].size > Danbooru.config.tag_query_limit
|
||||
|
||||
if q[:related].any? || q[:include].any?
|
||||
tag_query_sql << "!(" + escape_string_for_tsquery(q[:exclude]).join(" | ") + ")"
|
||||
else
|
||||
raise SearchError.new("You cannot search for only excluded tags")
|
||||
end
|
||||
end
|
||||
|
||||
if tag_query_sql.any?
|
||||
relation.where("posts.tag_index @@ to_tsquery('danbooru', E'" + tag_query_sql.join(" & ") + "')")
|
||||
end
|
||||
add_tag_string_search_relation(q[:tags], relation)
|
||||
|
||||
if q[:rating] == "q"
|
||||
relation.where("posts.rating = 'q'")
|
||||
@@ -398,7 +340,7 @@ class Post < ActiveRecord::Base
|
||||
elsif q[:rating_negated] == "e"
|
||||
relation.where("posts.rating <> 'e'")
|
||||
end
|
||||
|
||||
|
||||
case q[:order]
|
||||
when "id", "id_asc"
|
||||
relation.order("posts.id")
|
||||
@@ -455,19 +397,35 @@ class Post < ActiveRecord::Base
|
||||
|
||||
module UploaderMethods
|
||||
def uploader_id=(user_id)
|
||||
self.uploader_string = "user:#{user_id}"
|
||||
self.uploader = User.find(user_id)
|
||||
end
|
||||
|
||||
def uploader_id
|
||||
uploader_string[5, 100].to_i
|
||||
uploader.id
|
||||
end
|
||||
|
||||
def uploader_name
|
||||
uploader_string[5..-1]
|
||||
end
|
||||
|
||||
def uploader
|
||||
User.find(uploader_id)
|
||||
User.find_by_name(uploader_name)
|
||||
end
|
||||
|
||||
def uploader=(user)
|
||||
self.uploader_id = user.id
|
||||
self.uploader_string = "user:#{usern.name}"
|
||||
end
|
||||
end
|
||||
|
||||
module PoolMethods
|
||||
def add_pool(pool)
|
||||
self.pool_string += " pool:#{pool.name}"
|
||||
self.pool_string.strip!
|
||||
end
|
||||
|
||||
def remove_pool(user_id)
|
||||
self.pool_string.gsub!(/pool:#{pool.name}\b\s*/, " ")
|
||||
self.pool_string.strip!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -479,4 +437,5 @@ class Post < ActiveRecord::Base
|
||||
include TagMethods
|
||||
include FavoriteMethods
|
||||
include UploaderMethods
|
||||
include PoolMethods
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Tag < ActiveRecord::Base
|
||||
attr_accessible :category
|
||||
after_save :update_category_cache
|
||||
named_scope :by_pattern, lambda {|name| where(["name LIKE ? ESCAPE E'\\\\'", name.to_escaped_for_sql_like])}
|
||||
|
||||
class CategoryMapping
|
||||
Danbooru.config.reverse_tag_category_mapping.each do |value, category|
|
||||
@@ -30,26 +31,19 @@ class Tag < ActiveRecord::Base
|
||||
@category_mapping ||= CategoryMapping.new
|
||||
end
|
||||
|
||||
def select_category_for(tag_name)
|
||||
select_value_sql("SELECT category FROM tags WHERE name = ?", tag_name).to_i
|
||||
end
|
||||
|
||||
def category_for(tag_name)
|
||||
Cache.get("tc:#{Cache.sanitize(tag_name)}") do
|
||||
select_value_sql("SELECT category FROM tags WHERE name = ?", tag_name).to_i
|
||||
select_category_for(tag_name)
|
||||
end
|
||||
end
|
||||
|
||||
def categories_for(tag_names)
|
||||
key_hash = tag_names.inject({}) do |hash, x|
|
||||
hash[x] = "tc:#{Cache.sanitize(x)}"
|
||||
hash
|
||||
end
|
||||
categories_hash = MEMCACHE.get_multi(key_hash.values)
|
||||
returning({}) do |result_hash|
|
||||
key_hash.each do |tag_name, hash_key|
|
||||
if categories_hash.has_key?(hash_key)
|
||||
result_hash[tag_name] = categories_hash[hash_key]
|
||||
else
|
||||
result_hash[tag_name] = category_for(tag_name)
|
||||
end
|
||||
end
|
||||
Cache.get_multi(tag_names, "tc") do |name|
|
||||
select_category_for(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -191,63 +185,110 @@ class Tag < ActiveRecord::Base
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def parse_tag(tag, output)
|
||||
if tag[0] == "-" && tag.size > 1
|
||||
output[:exclude] << tag[1..-1]
|
||||
|
||||
elsif tag =~ /\*/
|
||||
matches = Tag.by_pattern(tag).all(:select => "name", :limit => 25, :order => "post_count DESC").map(&:name)
|
||||
matches = ["~no_matches~"] if matches.empty?
|
||||
output[:include] += matches
|
||||
|
||||
else
|
||||
output[:related] << token
|
||||
end
|
||||
end
|
||||
|
||||
def parse_query(query, options = {})
|
||||
q = Hash.new {|h, k| h[k] = []}
|
||||
q[:tags] = {
|
||||
:related => [],
|
||||
:include => [],
|
||||
:exclude => []
|
||||
}
|
||||
|
||||
scan_query(query).each do |token|
|
||||
if token =~ /\A(sub|md5|-rating|rating|width|height|mpixels|score|filesize|source|id|date|order|change|status|tagcount|gentagcount|arttagcount|chartagcount|copytagcount):(.+)\Z/
|
||||
if $1 == "sub"
|
||||
q[:subscriptions] = $2
|
||||
elsif $1 == "md5"
|
||||
q[:md5] = $2
|
||||
elsif $1 == "-rating"
|
||||
if token =~ /\A(-uploader|uploader|-pool|pool|-fav|fav|sub|md5|-rating|rating|width|height|mpixels|score|filesize|source|id|date|order|status|tagcount|gentags|arttags|chartags|copytags):(.+)\Z/
|
||||
case $1
|
||||
when "-uploader"
|
||||
q[:tags][:exclude] << token[1..-1]
|
||||
|
||||
when "uploader"
|
||||
q[:tags][:related] << token
|
||||
|
||||
when "-pool"
|
||||
q[:tags][:exclude] << token[1..-1]
|
||||
|
||||
when "pool"
|
||||
q[:tags][:related] << token
|
||||
|
||||
when "-fav"
|
||||
q[:tags][:exclude] << token[1..-1]
|
||||
|
||||
when "fav"
|
||||
q[:tags][:related] << token
|
||||
|
||||
when "sub"
|
||||
q[:subscriptions] << $2
|
||||
|
||||
when "md5"
|
||||
q[:md5] = $2.split(/,/)
|
||||
|
||||
when "-rating"
|
||||
q[:rating_negated] = $2
|
||||
elsif $1 == "rating"
|
||||
|
||||
when "rating"
|
||||
q[:rating] = $2
|
||||
elsif $1 == "id"
|
||||
|
||||
when "id"
|
||||
q[:post_id] = parse_helper($2)
|
||||
elsif $1 == "width"
|
||||
|
||||
when "width"
|
||||
q[:width] = parse_helper($2)
|
||||
elsif $1 == "height"
|
||||
|
||||
when "height"
|
||||
q[:height] = parse_helper($2)
|
||||
elsif $1 == "mpixels"
|
||||
|
||||
when "mpixels"
|
||||
q[:mpixels] = parse_helper($2, :float)
|
||||
elsif $1 == "score"
|
||||
|
||||
when "score"
|
||||
q[:score] = parse_helper($2)
|
||||
elsif $1 == "filesize"
|
||||
|
||||
when "filesize"
|
||||
q[:filesize] = parse_helper($2, :filesize)
|
||||
elsif $1 == "source"
|
||||
|
||||
when "source"
|
||||
q[:source] = $2.to_escaped_for_sql_like + "%"
|
||||
elsif $1 == "date"
|
||||
|
||||
when "date"
|
||||
q[:date] = parse_helper($2, :date)
|
||||
elsif $1 == "tagcount"
|
||||
|
||||
when "tagcount"
|
||||
q[:tag_count] = parse_helper($2)
|
||||
elsif $1 == "gentagcount"
|
||||
|
||||
when "gentags"
|
||||
q[:general_tag_count] = parse_helper($2)
|
||||
elsif $1 == "arttagcount"
|
||||
|
||||
when "arttags"
|
||||
q[:artist_tag_count] = parse_helper($2)
|
||||
elsif $1 == "chartagcount"
|
||||
|
||||
when "chartags"
|
||||
q[:character_tag_count] = parse_helper($2)
|
||||
elsif $1 == "copytagcount"
|
||||
|
||||
when "copytags"
|
||||
q[:copyright_tag_count] = parse_helper($2)
|
||||
elsif $1 == "order"
|
||||
|
||||
when "order"
|
||||
q[:order] = $2
|
||||
elsif $1 == "change"
|
||||
q[:change] = parse_helper($2)
|
||||
elsif $1 == "status"
|
||||
|
||||
when "status"
|
||||
q[:status] = $2
|
||||
end
|
||||
elsif token[0] == "-" && token.size > 1
|
||||
q[:exclude] << token[1..-1]
|
||||
elsif token[0] == "~" && token.size > 1
|
||||
q[:include] << token[1..-1]
|
||||
elsif token.include?("*")
|
||||
matches = where(["name LIKE ? ESCAPE E'\\\\'", token.to_escaped_for_sql_like]).all(:select => "name", :limit => 25, :order => "post_count DESC").map(&:name)
|
||||
matches = ["~no_matches~"] if matches.empty?
|
||||
q[:include] += matches
|
||||
|
||||
else
|
||||
q[:related] << token
|
||||
parse_tag(token, q[:tags])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -257,9 +298,9 @@ class Tag < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def normalize_tags_in_query(query_hash)
|
||||
query_hash[:exclude] = TagAlias.to_aliased(query_hash[:exclude], :strip_prefix => true) if query_hash.has_key?(:exclude)
|
||||
query_hash[:include] = TagAlias.to_aliased(query_hash[:include], :strip_prefix => true) if query_hash.has_key?(:include)
|
||||
query_hash[:related] = TagAlias.to_aliased(query_hash[:related]) if query_hash.has_key?(:related)
|
||||
query_hash[:tags][:exclude] = TagAlias.to_aliased(query_hash[:tags][:exclude])
|
||||
query_hash[:tags][:include] = TagAlias.to_aliased(query_hash[:tags][:include])
|
||||
query_hash[:tags][:related] = TagAlias.to_aliased(query_hash[:tags][:related])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
17
app/models/tag_alias.rb
Normal file
17
app/models/tag_alias.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class TagAlias < ActiveRecord::Base
|
||||
after_save :update_posts
|
||||
|
||||
def self.to_aliased(names)
|
||||
alias_hash = Cache.get_multi(names, "ta") do |name|
|
||||
ta = TagAlias.find_by_antecedent_name(name)
|
||||
if ta
|
||||
ta.consequent_name
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_posts
|
||||
end
|
||||
end
|
||||
17
app/models/tag_implication.rb
Normal file
17
app/models/tag_implication.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class TagImplication < ActiveRecord::Base
|
||||
def after_save :update_descendant_names
|
||||
|
||||
def descendants
|
||||
all = []
|
||||
children = [consequent_name]
|
||||
|
||||
until children.empty?
|
||||
all += children
|
||||
children = where(["antecedent_name IN (?)", children]).all.map(&:consequent_name)
|
||||
end
|
||||
end
|
||||
|
||||
def update_desecendant_names
|
||||
self.descendant_names = descendants.join(" ")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user