added notes

This commit is contained in:
albert
2010-02-24 15:40:55 -05:00
parent 7bb935256b
commit 55700efeb1
17 changed files with 462 additions and 27 deletions

View File

@@ -1,22 +1,23 @@
class RelatedTagCalculator
def find_tags(tag, limit)
ActiveRecord::Base.connection.select_values_sql("SELECT tag_string FROM posts WHERE tag_index @@ to_tsquery('danbooru', ?) ORDER BY id DESC LIMIT ?", tag, limit)
ActiveRecord::Base.select_values_sql("SELECT tag_string FROM posts WHERE tag_index @@ to_tsquery('danbooru', ?) ORDER BY id DESC LIMIT ?", tag, limit)
end
def calculate_from_sample(name, limit, category_constraint = nil)
counts = Hash.new {|h, k| h[k] = 0}
find_tags(name, limit).each do |post|
find_tags(name, limit).each do |tags|
tag_array = Tag.scan_tags(tags)
if category_constraint
categories = Tag.categories_for(post.tag_array)
categories = Tag.categories_for(tag_array)
post.tag_array.each do |tag|
tag_array.each do |tag|
if categories[tag] == category_constraint && tag != name
counts[tag] += 1
end
end
else
post.tag_array.each do |tag|
tag_array.each do |tag|
if tag != name
counts[tag] += 1
end

View File

@@ -5,6 +5,7 @@ class Artist < ActiveRecord::Base
after_save :create_version
after_save :commit_url_string
validates_uniqueness_of :name
validates_presence_of :updater_id, :updater_ip_addr
belongs_to :updater, :class_name => "User"
belongs_to :creator, :class_name => "User"
has_many :members, :class_name => "Artist", :foreign_key => "group_name", :primary_key => "name"
@@ -137,12 +138,14 @@ class Artist < ActiveRecord::Base
)
end
def revert_to!(version)
def revert_to!(version, reverter_id, reverter_ip_addr)
self.name = version.name
self.url_string = version.url_string
self.is_active = version.is_active
self.other_names = version.other_names
self.group_name = version.group_name
self.updater_id = reverter_id
self.updater_ip_addr = reverter_ip_addr
save
end
end

109
app/models/note.rb Normal file
View File

@@ -0,0 +1,109 @@
class Note < ActiveRecord::Base
attr_accessor :updater_id, :updater_ip_addr
belongs_to :post
belongs_to :creator, :class_name => "User"
belongs_to :updater, :class_name => "User"
before_save :initialize_creator
before_save :blank_body
has_many :versions, :class_name => "NoteVersion"
after_save :update_post
after_save :create_version
validate :post_must_not_be_note_locked
validates_presence_of :updater_id, :updater_ip_addr
attr_accessible :x, :y, :width, :height, :body, :updater_id, :updater_ip_addr, :is_active
scope :active, where("is_active = TRUE")
def presenter
@presenter ||= NotePresenter.new(self)
end
def initialize_creator
self.creator_id = updater_id
end
def post_must_not_be_note_locked
if is_locked?
errors.add :post, "is note locked"
return false
end
end
def is_locked?
Post.exists?(["id = ? AND is_note_locked = ?", post_id, true])
end
def blank_body
self.body = "(empty)" if body.blank?
end
def creator_name
User.find_name(creator_id)
end
def update_post
if Note.exists?(["is_active = ? AND post_id = ?", true, post_id])
Post.update(post_id, :last_noted_at => updated_at, :updater_id => updater_id, :updater_ip_addr => updater_ip_addr)
else
Post.update(post_id, :last_noted_at => nil, :updater_id => updater_id, :updater_ip_addr => updater_ip_addr)
end
end
def create_version
versions.create(
:updater_id => updater_id,
:updater_ip_addr => updater_ip_addr,
:x => x,
:y => y,
:width => width,
:height => height,
:is_active => is_active,
:body => body
)
end
def revert_to(version, reverter_id, reverter_ip_addr)
self.x = version.x
self.y = version.y
self.body = version.body
self.width = version.width
self.height = version.height
self.is_active = version.is_active
self.updater_id = reverter_id
self.updater_ip_addr = reverter_ip_addr
end
def revert_to!(version, reverter_id, reverter_ip_addr)
revert_to(version, reverter_id, reverter_ip_addr)
save!
end
def self.undo_changes_by_user(user_id, reverter_id, reverter_ip_addr)
transaction do
notes = Note.joins(:versions).where(["note_versions.updater_id = ?", user_id]).select("DISTINCT notes.*").all
NoteVersion.destroy_all(["updater_id = ?", user_id])
notes.each do |note|
first = note.versions.first
if first
note.revert_to!(first, reverter_id, reverter_ip_addr)
end
end
end
end
def self.build_relation(params)
relation = where()
if !params[:query].blank?
query = params[:query].scan(/\S+/).join(" & ")
relation = relation.where(["text_index @@ plainto_tsquery(?)", query])
end
if params[:status] == "Active"
relation = relation.where("is_active = TRUE")
elsif params[:status] == "Deleted"
relation = relation.where("is_active = FALSE")
end
relation
end
end

View File

@@ -0,0 +1,5 @@
class NoteVersion < ActiveRecord::Base
def updater_name
User.find_name(updater_id)
end
end

View File

@@ -14,7 +14,9 @@ class Post < ActiveRecord::Base
has_one :moderation_detail, :class_name => "PostModerationDetail", :dependent => :destroy
has_many :versions, :class_name => "PostVersion", :dependent => :destroy
has_many :votes, :class_name => "PostVote", :dependent => :destroy
attr_accessible :source, :rating, :tag_string, :old_tag_string, :updater_id, :updater_ip_addr
has_many :notes, :dependent => :destroy
validates_presence_of :updater_id, :updater_ip_addr
attr_accessible :source, :rating, :tag_string, :old_tag_string, :updater_id, :updater_ip_addr, :last_noted_at
module FileMethods
def delete_files
@@ -134,14 +136,16 @@ class Post < ActiveRecord::Base
:updater_id => updater_id,
:updater_ip_addr => updater_ip_addr
)
raise PostVersion::Error.new(version.errors.full_messages.join("; ")) if version.errors.any?
end
def revert_to!(version)
def revert_to!(version, reverter_id, reverter_ip_addr)
self.source = version.source
self.rating = version.rating
self.tag_string = version.tag_string
self.updater_id = reverter_id
self.updater_ip_addr = reverter_ip_addr
save!
end
end

View File

@@ -27,14 +27,16 @@ class WikiPage < ActiveRecord::Base
relation
end
def revert_to(version)
def revert_to(version, reverter_id, reverter_ip_addr)
self.title = version.title
self.body = version.body
self.is_locked = version.is_locked
self.updater_id = reverter_id
self.updater_ip_addr = reverter_ip_addr
end
def revert_to!(version)
revert_to(version)
def revert_to!(version, reverter_id, reverter_ip_addr)
revert_to(version, reverter_id, reverter_ip_addr)
save!
end
@@ -51,13 +53,15 @@ class WikiPage < ActiveRecord::Base
end
def create_version
versions.create(
:updater_id => updater_id,
:updater_ip_addr => updater_ip_addr,
:title => title,
:body => body,
:is_locked => is_locked
)
if title_changed? || body_changed? || is_locked_changed?
versions.create(
:updater_id => updater_id,
:updater_ip_addr => updater_ip_addr,
:title => title,
:body => body,
:is_locked => is_locked
)
end
end
def initialize_creator

View File

@@ -0,0 +1,9 @@
class NotePresenter
def initialize(note)
@note = note
end
def formatted_body
note.body.gsub(/<tn>(.+?)<\/tn>/m, '<br><p class="tn">\1</p>').gsub(/\n/, '<br>')
end
end

View File

@@ -797,6 +797,84 @@ CREATE SEQUENCE jobs_id_seq
ALTER SEQUENCE jobs_id_seq OWNED BY jobs.id;
--
-- Name: note_versions; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE note_versions (
id integer NOT NULL,
note_id integer NOT NULL,
updater_id integer NOT NULL,
updater_ip_addr inet NOT NULL,
x integer NOT NULL,
y integer NOT NULL,
width integer NOT NULL,
height integer NOT NULL,
is_active boolean DEFAULT true NOT NULL,
body text NOT NULL,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
--
-- Name: note_versions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE note_versions_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
--
-- Name: note_versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE note_versions_id_seq OWNED BY note_versions.id;
--
-- Name: notes; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE notes (
id integer NOT NULL,
creator_id integer NOT NULL,
post_id integer NOT NULL,
x integer NOT NULL,
y integer NOT NULL,
width integer NOT NULL,
height integer NOT NULL,
is_active boolean DEFAULT true NOT NULL,
body text NOT NULL,
text_index tsvector NOT NULL,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
--
-- Name: notes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE notes_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
--
-- Name: notes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE notes_id_seq OWNED BY notes.id;
--
-- Name: pool_versions; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
@@ -1520,6 +1598,20 @@ ALTER TABLE forum_topics ALTER COLUMN id SET DEFAULT nextval('forum_topics_id_se
ALTER TABLE jobs ALTER COLUMN id SET DEFAULT nextval('jobs_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE note_versions ALTER COLUMN id SET DEFAULT nextval('note_versions_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE notes ALTER COLUMN id SET DEFAULT nextval('notes_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -1801,6 +1893,22 @@ ALTER TABLE ONLY jobs
ADD CONSTRAINT jobs_pkey PRIMARY KEY (id);
--
-- Name: note_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY note_versions
ADD CONSTRAINT note_versions_pkey PRIMARY KEY (id);
--
-- Name: notes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY notes
ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
--
-- Name: pool_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
@@ -2236,6 +2344,41 @@ CREATE INDEX index_forum_topics_on_creator_id ON forum_topics USING btree (creat
CREATE INDEX index_forum_topics_on_text_index ON forum_topics USING gin (text_index);
--
-- Name: index_note_versions_on_note_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_note_versions_on_note_id ON note_versions USING btree (note_id);
--
-- Name: index_note_versions_on_updater_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_note_versions_on_updater_id ON note_versions USING btree (updater_id);
--
-- Name: index_notes_on_creator_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_notes_on_creator_id ON notes USING btree (creator_id);
--
-- Name: index_notes_on_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_notes_on_post_id ON notes USING btree (post_id);
--
-- Name: index_notes_on_text_index; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_notes_on_text_index ON notes USING gin (text_index);
--
-- Name: index_pool_versions_on_pool_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
@@ -2503,6 +2646,16 @@ CREATE TRIGGER trigger_forum_topics_on_update
EXECUTE PROCEDURE tsvector_update_trigger('text_index', 'pg_catalog.english', 'title');
--
-- Name: trigger_notes_on_update; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER trigger_notes_on_update
BEFORE INSERT OR UPDATE ON notes
FOR EACH ROW
EXECUTE PROCEDURE tsvector_update_trigger('text_index', 'pg_catalog.english', 'body');
--
-- Name: trigger_posts_on_tag_index_update; Type: TRIGGER; Schema: public; Owner: -
--
@@ -2579,4 +2732,8 @@ INSERT INTO schema_migrations (version) VALUES ('20100221005812');
INSERT INTO schema_migrations (version) VALUES ('20100221012656');
INSERT INTO schema_migrations (version) VALUES ('20100223001012');
INSERT INTO schema_migrations (version) VALUES ('20100223001012');
INSERT INTO schema_migrations (version) VALUES ('20100224171915');
INSERT INTO schema_migrations (version) VALUES ('20100224172146');

View File

@@ -0,0 +1,25 @@
class CreateNotes < ActiveRecord::Migration
def self.up
create_table :notes do |t|
t.column :creator_id, :integer, :null => false
t.column :post_id, :integer, :null => false
t.column :x, :integer, :null => false
t.column :y, :integer, :null => false
t.column :width, :integer, :null => false
t.column :height, :integer, :null => false
t.column :is_active, :boolean, :null => false, :default => true
t.column :body, :text, :null => false
t.column :text_index, "tsvector", :null => false
t.timestamps
end
add_index :notes, :creator_id
add_index :notes, :post_id
execute "CREATE INDEX index_notes_on_text_index ON notes USING GIN (text_index)"
execute "CREATE TRIGGER trigger_notes_on_update BEFORE INSERT OR UPDATE ON notes FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('text_index', 'pg_catalog.english', 'body')"
end
def self.down
drop_table :notes
end
end

View File

@@ -0,0 +1,23 @@
class CreateNoteVersions < ActiveRecord::Migration
def self.up
create_table :note_versions do |t|
t.column :note_id, :integer, :null => false
t.column :updater_id, :integer, :null => false
t.column :updater_ip_addr, "inet", :null => false
t.column :x, :integer, :null => false
t.column :y, :integer, :null => false
t.column :width, :integer, :null => false
t.column :height, :integer, :null => false
t.column :is_active, :boolean, :null => false, :default => true
t.column :body, :text, :null => false
t.timestamps
end
add_index :note_versions, :note_id
add_index :note_versions, :updater_id
end
def self.down
drop_table :note_versions
end
end

12
test/factories/note.rb Normal file
View File

@@ -0,0 +1,12 @@
Factory.define(:note) do |f|
f.creator {|x| x.association(:user)}
f.post {|x| x.association(:post)}
f.x 0
f.y 0
f.width 0
f.height 0
f.is_active true
f.body {Faker::Lorem.sentences.join}
f.updater_id {|x| x.creator_id}
f.updater_ip_addr "127.0.0.1"
end

View File

@@ -89,6 +89,7 @@ class ArtistTest < ActiveSupport::TestCase
should "revert to prior versions" do
user = Factory.create(:user)
reverter = Factory.create(:user)
artist = nil
assert_difference("ArtistVersion.count") do
artist = Factory.create(:artist, :other_names => "yyy")
@@ -103,7 +104,7 @@ class ArtistTest < ActiveSupport::TestCase
first_version = ArtistVersion.first
assert_equal("yyy", first_version.other_names)
artist.revert_to!(first_version)
artist.revert_to!(first_version, reverter.id, "127.0.0.1")
artist.reload
assert_equal("yyy", artist.other_names)
end

72
test/unit/note_test.rb Normal file
View File

@@ -0,0 +1,72 @@
require File.dirname(__FILE__) + '/../test_helper'
class NoteTest < ActiveSupport::TestCase
context "A note" do
setup do
MEMCACHE.flush_all
end
should "create versions" do
note = nil
assert_difference("NoteVersion.count") do
note = Factory.create(:note)
end
version = NoteVersion.last
assert_equal(note.body, version.body)
assert_difference("NoteVersion.count") do
note.update_attributes(:updater_id => note.creator_id, :updater_ip_addr => "127.0.0.1", :body => "fafafa")
end
version = NoteVersion.last
assert_equal("fafafa", version.body)
end
should "allow undoing any change from a user" do
vandal = Factory.create(:user)
reverter = Factory.create(:user)
note = Factory.create(:note, :x => 100, :y => 100)
note.update_attributes(:x => 2000, :y => 2000, :updater_id => vandal.id, :updater_ip_addr => "127.0.0.1")
note.reload
assert_equal(2000, note.x)
assert_equal(2000, note.y)
Note.undo_changes_by_user(vandal.id, reverter.id, "127.0.0.1")
note.reload
assert_equal(100, note.x)
assert_equal(100, note.y)
end
should "not validate if the post is note locked" do
post = Factory.create(:post, :is_note_locked => true)
note = Factory.build(:note, :post => post)
assert_difference("Note.count", 0) do
note.save
end
assert(note.errors.any?)
end
should "update the post when saved" do
post = Factory.create(:post)
assert_nil(post.last_noted_at)
note = Factory.create(:note, :post => post)
post.reload
assert_not_nil(post.last_noted_at)
end
should "know when the post is note locked" do
post = Factory.create(:post, :is_note_locked => true)
note = Factory.build(:note, :post => post)
assert(note.is_locked?)
end
should "return hits when searched" do
notes = []
notes << Factory.create(:note, :body => "aaa bbb ccc")
notes << Factory.create(:note, :body => "bbb ccc ddd", :is_active => false)
notes << Factory.create(:note, :body => "eee")
results = Note.build_relation(:query => "bbb").all
assert_equal(2, results.size)
results = Note.build_relation(:query => "bbb", :status => "Active").all
assert_equal(1, results.size)
assert_equal(notes[0].id, results[0].id)
end
end
end

View File

@@ -0,0 +1,8 @@
require 'test_helper'
class NoteVersionTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@@ -39,6 +39,7 @@ class PostTest < ActiveSupport::TestCase
should "be created on any save" do
@user = Factory.create(:user)
@post = Factory.create(:post)
@reverter = Factory.create(:user)
assert_equal(1, @post.versions.size)
@post.rating = "e"
@@ -49,7 +50,7 @@ class PostTest < ActiveSupport::TestCase
assert_equal(@user.id, @post.versions.last.updater_id)
assert_equal("125.0.0.0", @post.versions.last.updater_ip_addr)
@post.revert_to!(PostVersion.first)
@post.revert_to!(PostVersion.first, @reverter.id, "127.0.0.1")
assert_equal("tag1 tag2", @post.tag_string)
assert_equal("q", @post.rating)
end

View File

@@ -10,7 +10,7 @@ class RelatedTagCalculatorTest < ActiveSupport::TestCase
tag = Tag.find_by_name("aaa")
calculator = RelatedTagCalculator.new
assert_equal({"bbb" => 3, "ccc" => 2, "ddd" => 1}, calculator.calculate_from_sample("aaa"))
assert_equal({"bbb" => 3, "ccc" => 2, "ddd" => 1}, calculator.calculate_from_sample("aaa", 10))
end
should "calculate related tags for a tag" do
@@ -21,9 +21,9 @@ class RelatedTagCalculatorTest < ActiveSupport::TestCase
tag = Tag.find_by_name("aaa")
calculator = RelatedTagCalculator.new
assert_equal({"ccc" => 2}, calculator.calculate_from_sample("aaa", Tag.categories.artist))
assert_equal({"ccc" => 2}, calculator.calculate_from_sample("aaa", 10, Tag.categories.artist))
calculator = RelatedTagCalculator.new
assert_equal({"ddd" => 1}, calculator.calculate_from_sample("aaa", Tag.categories.copyright))
assert_equal({"ddd" => 1}, calculator.calculate_from_sample("aaa", 10, Tag.categories.copyright))
end
should "convert a hash into string format" do
@@ -34,7 +34,7 @@ class RelatedTagCalculatorTest < ActiveSupport::TestCase
tag = Tag.find_by_name("aaa")
calculator = RelatedTagCalculator.new
counts = calculator.calculate_from_sample("aaa")
counts = calculator.calculate_from_sample("aaa", 10)
assert_equal("bbb 3 ccc 2 ddd 1", calculator.convert_hash_to_string(counts))
end
end

View File

@@ -21,6 +21,7 @@ class WikiPageTest < ActiveSupport::TestCase
should "create versions" do
wp = nil
user = Factory.create(:user)
reverter = Factory.create(:user)
assert_difference("WikiPageVersion.count") do
wp = Factory.create(:wiki_page, :title => "xxx")
@@ -34,7 +35,7 @@ class WikiPageTest < ActiveSupport::TestCase
end
version = WikiPageVersion.first
wp.revert_to!(version)
wp.revert_to!(version, reverter.id, "127.0.0.1")
assert_equal("xxx", wp.title)
end