tag unit test

This commit is contained in:
Albert Yi 2010-02-06 23:11:26 -05:00
parent 305f4d4607
commit 341a24e22e
8 changed files with 403 additions and 11 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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');

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

View File

@ -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

View File

@ -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