forked from e621ng/e621ng
tag unit test
This commit is contained in:
parent
305f4d4607
commit
341a24e22e
@ -1,2 +1,224 @@
|
||||
class Tag < ActiveRecord::Base
|
||||
class CategoryMapping
|
||||
Danbooru.config.reverse_tag_category_mapping.each do |value, category|
|
||||
define_method(category.downcase) do
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def regexp
|
||||
@regexp ||= Regexp.compile(Danbooru.config.tag_category_mapping.keys.sort_by {|x| -x.size}.join("|"))
|
||||
end
|
||||
|
||||
def value_for(string)
|
||||
Danbooru.config.tag_category_mapping[string.downcase] || 0
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessible :category
|
||||
|
||||
after_save {|rec| Cache.put("tag_type:#{cache_safe_name}", rec.category_name)}
|
||||
|
||||
|
||||
### Category Methods ###
|
||||
def self.categories
|
||||
@category_mapping ||= CategoryMapping.new
|
||||
end
|
||||
|
||||
def category_name
|
||||
Danbooru.config.reverse_tag_category_mapping[category]
|
||||
end
|
||||
|
||||
|
||||
### Statistics Methods ###
|
||||
def self.trending
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
### Name Methods ###
|
||||
def self.normalize_name(name)
|
||||
name.downcase.tr(" ", "_").gsub(/\A[-~*]+/, "")
|
||||
end
|
||||
|
||||
def self.find_or_create_by_name(name, options = {})
|
||||
name = normalize_name(name)
|
||||
category = self.class.types.general
|
||||
|
||||
if name =~ /\A(#{categories.regexp}):(.+)\Z/
|
||||
category = self.class.types.value_for($1)
|
||||
end
|
||||
|
||||
tag = find_by_name(name)
|
||||
|
||||
if tag
|
||||
if category > 0 && !(options[:user] && !options[:user].is_privileged? && tag.post_count > 10)
|
||||
tag.update_attribute(:category, category)
|
||||
end
|
||||
|
||||
tag
|
||||
else
|
||||
returning Tag.new do |tag|
|
||||
tag.name = name
|
||||
tag.category = category
|
||||
tag.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cache_safe_name
|
||||
name.gsub(/[^a-zA-Z0-9_-]/, "_")
|
||||
end
|
||||
|
||||
|
||||
### Update methods ###
|
||||
def self.mass_edit(start_tags, result_tags, updater_id, updater_ip_addr)
|
||||
raise NotImplementedError
|
||||
|
||||
Post.find_by_tags(start_tags).each do |p|
|
||||
start = TagAlias.to_aliased(scan_tags(start_tags))
|
||||
result = TagAlias.to_aliased(scan_tags(result_tags))
|
||||
tags = (p.cached_tags.scan(/\S+/) - start + result).join(" ")
|
||||
p.update_attributes(:updater_user_id => updater_id, :updater_ip_addr => updater_ip_addr, :tags => tags)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
### Parse Methods ###
|
||||
def self.scan_query(query)
|
||||
query.to_s.downcase.scan(/\S+/).uniq
|
||||
end
|
||||
|
||||
def self.scan_tags(tags)
|
||||
tags.to_s.downcase.gsub(/[&,;]/, "").scan(/\S+/).uniq
|
||||
end
|
||||
|
||||
def self.parse_cast(object, type)
|
||||
case type
|
||||
when :integer
|
||||
object.to_i
|
||||
|
||||
when :float
|
||||
object.to_f
|
||||
|
||||
when :date
|
||||
begin
|
||||
object.to_date
|
||||
rescue Exception
|
||||
nil
|
||||
end
|
||||
|
||||
when :filesize
|
||||
object =~ /^(\d+(?:\.\d*)?|\d*\.\d+)([kKmM]?)[bB]?$/
|
||||
|
||||
size = $1.to_f
|
||||
unit = $2
|
||||
|
||||
conversion_factor = case unit
|
||||
when /m/i
|
||||
1024 * 1024
|
||||
when /k/i
|
||||
1024
|
||||
else
|
||||
1
|
||||
end
|
||||
|
||||
(size * conversion_factor).to_i
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_helper(range, type = :integer)
|
||||
# "1", "0.5", "5.", ".5":
|
||||
# (-?(\d+(\.\d*)?|\d*\.\d+))
|
||||
case range
|
||||
when /^(.+?)\.\.(.+)/
|
||||
return [:between, parse_cast($1, type), parse_cast($2, type)]
|
||||
|
||||
when /^<=(.+)/, /^\.\.(.+)/
|
||||
return [:lte, parse_cast($1, type)]
|
||||
|
||||
when /^<(.+)/
|
||||
return [:lt, parse_cast($1, type)]
|
||||
|
||||
when /^>=(.+)/, /^(.+)\.\.$/
|
||||
return [:gte, parse_cast($1, type)]
|
||||
|
||||
when /^>(.+)/
|
||||
return [:gt, parse_cast($1, type)]
|
||||
|
||||
else
|
||||
return [:eq, parse_cast(range, type)]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_query(query, options = {})
|
||||
q = Hash.new {|h, k| h[k] = []}
|
||||
|
||||
scan_query(query).each do |token|
|
||||
if token =~ /^(sub|md5|-rating|rating|width|height|mpixels|score|filesize|source|id|date|order|change|status|tagcount|gentagcount|arttagcount|chartagcount|copytagcount):(.+)$/
|
||||
if $1 == "user"
|
||||
q[:user] = $2
|
||||
elsif $1 == "fav"
|
||||
q[:fav] = $2
|
||||
elsif $1 == "sub"
|
||||
q[:subscriptions] = $2
|
||||
elsif $1 == "md5"
|
||||
q[:md5] = $2
|
||||
elsif $1 == "-rating"
|
||||
q[:rating_negated] = $2
|
||||
elsif $1 == "rating"
|
||||
q[:rating] = $2
|
||||
elsif $1 == "id"
|
||||
q[:post_id] = parse_helper($2)
|
||||
elsif $1 == "width"
|
||||
q[:width] = parse_helper($2)
|
||||
elsif $1 == "height"
|
||||
q[:height] = parse_helper($2)
|
||||
elsif $1 == "mpixels"
|
||||
q[:mpixels] = parse_helper($2, :float)
|
||||
elsif $1 == "score"
|
||||
q[:score] = parse_helper($2)
|
||||
elsif $1 == "filesize"
|
||||
q[:filesize] = parse_helper($2, :filesize)
|
||||
elsif $1 == "source"
|
||||
q[:source] = $2.to_escaped_for_sql_like + "%"
|
||||
elsif $1 == "date"
|
||||
q[:date] = parse_helper($2, :date)
|
||||
elsif $1 == "tagcount"
|
||||
q[:tag_count] = parse_helper($2)
|
||||
elsif $1 == "gentagcount"
|
||||
q[:general_tag_count] = parse_helper($2)
|
||||
elsif $1 == "arttagcount"
|
||||
q[:artist_tag_count] = parse_helper($2)
|
||||
elsif $1 == "chartagcount"
|
||||
q[:character_tag_count] = parse_helper($2)
|
||||
elsif $1 == "copytagcount"
|
||||
q[:copyright_tag_count] = parse_helper($2)
|
||||
elsif $1 == "order"
|
||||
q[:order] = $2
|
||||
elsif $1 == "change"
|
||||
q[:change] = parse_helper($2)
|
||||
elsif $1 == "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
|
||||
end
|
||||
end
|
||||
|
||||
q[:exclude] = TagAlias.to_aliased(q[:exclude], :strip_prefix => true) if q.has_key?(:exclude)
|
||||
q[:include] = TagAlias.to_aliased(q[:include], :strip_prefix => true) if q.has_key?(:include)
|
||||
q[:related] = TagAlias.to_aliased(q[:related]) if q.has_key?(:related)
|
||||
|
||||
return q
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,8 @@ require 'digest/sha1'
|
||||
class User < ActiveRecord::Base
|
||||
attr_accessor :password
|
||||
|
||||
attr_accessible :password_hash, :email, :last_logged_in_at, :last_forum_read_at, :has_mail, :receive_email_notifications, :comment_threshold, :always_resize_images, :favorite_tags, :blacklisted_tags
|
||||
|
||||
validates_length_of :name, :within => 2..20, :on => :create
|
||||
validates_format_of :name, :with => /\A[^\s;,]+\Z/, :on => :create, :message => "cannot have whitespace, commas, or semicolons"
|
||||
validates_uniqueness_of :name, :case_sensitive => false, :on => :create
|
||||
|
@ -57,6 +57,11 @@ module Danbooru
|
||||
1024
|
||||
end
|
||||
|
||||
# When calculating statistics based on the posts table, gather this many posts to sample from.
|
||||
def post_sample_size
|
||||
300
|
||||
end
|
||||
|
||||
# List of memcached servers
|
||||
def memcached_servers
|
||||
%w(localhost:11211)
|
||||
@ -102,8 +107,40 @@ module Danbooru
|
||||
5
|
||||
end
|
||||
|
||||
# The name of the server the app is hosted on.
|
||||
def server_host
|
||||
Socket.gethostname
|
||||
end
|
||||
|
||||
# Returns a hash mapping various tag categories to a numerical value.
|
||||
# Be sure to update the reverse_tag_category_mapping also.
|
||||
def tag_category_mapping
|
||||
@tag_category_mapping ||= {
|
||||
"general" => 0,
|
||||
"gen" => 0,
|
||||
|
||||
"artist" => 1,
|
||||
"art" => 1,
|
||||
|
||||
"copyright" => 3,
|
||||
"copy" => 3,
|
||||
"co" => 3,
|
||||
|
||||
"character" => 4,
|
||||
"char" => 4,
|
||||
"ch" => 4
|
||||
}
|
||||
end
|
||||
|
||||
# Returns a hash maping numerical category values to their
|
||||
# string equivalent. Be sure to update the tag_category_mapping also.
|
||||
def reverse_tag_category_mapping
|
||||
@reverse_tag_category_mapping ||= {
|
||||
0 => "General",
|
||||
1 => "Artist",
|
||||
3 => "Copyright",
|
||||
4 => "Character"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -24,6 +24,40 @@ CREATE TABLE schema_migrations (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tags; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE tags (
|
||||
id integer NOT NULL,
|
||||
name character varying(255) NOT NULL,
|
||||
post_count integer DEFAULT 0 NOT NULL,
|
||||
category integer DEFAULT 0 NOT NULL,
|
||||
related_tags text,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE tags_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE tags_id_seq OWNED BY tags.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@ -72,6 +106,13 @@ CREATE SEQUENCE users_id_seq
|
||||
ALTER SEQUENCE users_id_seq OWNED BY users.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE tags ALTER COLUMN id SET DEFAULT nextval('tags_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@ -79,6 +120,14 @@ ALTER SEQUENCE users_id_seq OWNED BY users.id;
|
||||
ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY tags
|
||||
ADD CONSTRAINT tags_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@ -87,6 +136,13 @@ ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_tags_on_name; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX index_tags_on_name ON tags USING btree (name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@ -112,4 +168,6 @@ CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (v
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100204211522');
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100204211522');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100205162521');
|
17
db/migrate/20100205162521_create_tags.rb
Normal file
17
db/migrate/20100205162521_create_tags.rb
Normal file
@ -0,0 +1,17 @@
|
||||
class CreateTags < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :tags do |t|
|
||||
t.column :name, :string, :null => false
|
||||
t.column :post_count, :integer, :null => false, :default => 0
|
||||
t.column :category, :integer, :null => false, :default => 0
|
||||
t.column :related_tags, :text
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :tags, :name, :unique => true
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :tags
|
||||
end
|
||||
end
|
10
test/factories/tag.rb
Normal file
10
test/factories/tag.rb
Normal file
@ -0,0 +1,10 @@
|
||||
Factory.define(:tag) do |f|
|
||||
f.name {Faker::Name.first_name}
|
||||
f.post_count 0
|
||||
f.category Tag.categories.general
|
||||
f.related_tags ""
|
||||
end
|
||||
|
||||
Factory.define(:artist_tag, :parent => :tag) do |f|
|
||||
f.category Tag.categories.artist
|
||||
end
|
@ -4,26 +4,26 @@ Factory.define(:user) do |f|
|
||||
f.email {Faker::Internet.email}
|
||||
end
|
||||
|
||||
Factory.define(:banned_user) do |f|
|
||||
Factory.define(:banned_user, :parent => :user) do |f|
|
||||
f.is_banned true
|
||||
end
|
||||
|
||||
Factory.define(:privileged_user) do |f|
|
||||
Factory.define(:privileged_user, :parent => :user) do |f|
|
||||
f.is_privileged true
|
||||
end
|
||||
|
||||
Factory.define(:contributor_user) do |f|
|
||||
Factory.define(:contributor_user, :parent => :user) do |f|
|
||||
f.is_contributor true
|
||||
end
|
||||
|
||||
Factory.define(:janitor_user) do |f|
|
||||
Factory.define(:janitor_user, :parent => :user) do |f|
|
||||
f.is_janitor true
|
||||
end
|
||||
|
||||
Factory.define(:moderator_user) do |f|
|
||||
Factory.define(:moderator_user, :parent => :user) do |f|
|
||||
f.is_moderator true
|
||||
end
|
||||
|
||||
Factory.define(:admin_user) do |f|
|
||||
Factory.define(:admin_user, :parent => :user) do |f|
|
||||
f.is_admin true
|
||||
end
|
||||
|
@ -1,8 +1,54 @@
|
||||
require 'test_helper'
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class TagTest < ActiveSupport::TestCase
|
||||
# Replace this with your real tests.
|
||||
test "the truth" do
|
||||
assert true
|
||||
context "A tag category mapping" do
|
||||
setup do
|
||||
MEMCACHE.flush_all
|
||||
end
|
||||
|
||||
should "exist" do
|
||||
assert_nothing_raised {Tag.categories}
|
||||
end
|
||||
|
||||
should "have convenience methods for the four main categories" do
|
||||
assert_equal(0, Tag.categories.general)
|
||||
assert_equal(1, Tag.categories.artist)
|
||||
assert_equal(3, Tag.categories.copyright)
|
||||
assert_equal(4, Tag.categories.character)
|
||||
end
|
||||
|
||||
should "have a regular expression for matching category names and shortcuts" do
|
||||
regexp = Tag.categories.regexp
|
||||
|
||||
assert_match(regexp, "artist")
|
||||
assert_match(regexp, "art")
|
||||
assert_match(regexp, "copyright")
|
||||
assert_match(regexp, "copy")
|
||||
assert_match(regexp, "co")
|
||||
assert_match(regexp, "character")
|
||||
assert_match(regexp, "char")
|
||||
assert_match(regexp, "ch")
|
||||
assert_no_match(regexp, "c")
|
||||
assert_no_match(regexp, "woodle")
|
||||
end
|
||||
|
||||
should "map a category name to its value" do
|
||||
assert_equal(0, Tag.categories.value_for("general"))
|
||||
assert_equal(0, Tag.categories.value_for("gen"))
|
||||
assert_equal(1, Tag.categories.value_for("artist"))
|
||||
assert_equal(1, Tag.categories.value_for("art"))
|
||||
assert_equal(0, Tag.categories.value_for("unknown"))
|
||||
end
|
||||
end
|
||||
|
||||
context "A tag" do
|
||||
setup do
|
||||
MEMCACHE.flush_all
|
||||
end
|
||||
|
||||
should "know its category name" do
|
||||
@tag = Factory.create(:artist_tag)
|
||||
assert_equal("Artist", @tag.category_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user