|
|
@@ -0,0 +1,110 @@ |
|
|
require 'rubygems' |
|
|
require 'json' |
|
|
require 'redis' |
|
|
|
|
|
class RedisComments |
|
|
def initialize(redis,namespace,sort_proc=nil) |
|
|
@r = redis |
|
|
@namespace = namespace |
|
|
@sort_proc = sort_proc |
|
|
end |
|
|
|
|
|
def thread_key(thread_id) |
|
|
"thread:#{@namespace}:#{thread_id}" |
|
|
end |
|
|
|
|
|
def insert(thread_id,comment) |
|
|
raise "no parent_id field" if !comment.has_key?('parent_id') |
|
|
key = thread_key(thread_id) |
|
|
id = @r.hincrby(key,:nextid,1) |
|
|
@r.hset(key,id,comment.to_json) |
|
|
return id.to_i |
|
|
end |
|
|
|
|
|
def edit(thread_id,comment_id,comment) |
|
|
key = thread_key(thread_id) |
|
|
old = @r.hget(key,id) |
|
|
return false if !old |
|
|
comment['parent_id'] = JSON.parse(old)['parent_id'] |
|
|
@r.hset(key,id,comment.to_json) |
|
|
return true |
|
|
end |
|
|
|
|
|
|
|
|
def remove_thread(thread_id) |
|
|
@r.del(thread_key(thread_id)) |
|
|
end |
|
|
|
|
|
def comments_in_thread(thread_id) |
|
|
@r.hlen(thread_key(thread_id)).to_i-1 |
|
|
end |
|
|
|
|
|
def del_comment(thread_id,comment_id) |
|
|
# TODO? You may want to make sure there are no parents. |
|
|
# If there are parents we can call edit() with "comment removed" |
|
|
# or something like that. |
|
|
# |
|
|
# A probably wiser implementation is to *never* use this method |
|
|
# and instead flag the comment as deleted. Then when rendering we |
|
|
# can display it in a special way if there ara replies, otherwise |
|
|
# we can avoid displaying deleted comments that are leafs. |
|
|
@r.hdel(thread_key(thread_id),comment_id) |
|
|
end |
|
|
|
|
|
def render_comments(thread_id,&block) |
|
|
byparent = {} |
|
|
@r.hgetall(thread_key(thread_id)).each{|id,comment| |
|
|
next if id == "nextid" |
|
|
c = JSON.parse(comment) |
|
|
c['id'] = id.to_i |
|
|
parent_id = c['parent_id'].to_i |
|
|
byparent[parent_id] = [] if !byparent.has_key?(parent_id) |
|
|
byparent[parent_id] << c |
|
|
} |
|
|
|
|
|
render_comments_rec(byparent,-1,0,block) |
|
|
end |
|
|
|
|
|
def render_comments_rec(byparent,parent_id,level,block) |
|
|
thislevel = byparent[parent_id] |
|
|
thislevel = @sort_proc.call(thislevel,level) if @sort_proc |
|
|
thislevel.each{|c| |
|
|
c['level'] = level |
|
|
block.call(c) |
|
|
if byparent[c['id']] |
|
|
render_comments_rec(byparent,c['id'],level+1,block) |
|
|
end |
|
|
} |
|
|
end |
|
|
end |
|
|
|
|
|
# In this example we want comments at top level sorted in reversed chronological |
|
|
# order, but all the sub trees sorted in plain chronological order. |
|
|
comments = RedisComments.new(Redis.new,"mycomments",proc{|c,level| |
|
|
if level == 0 |
|
|
c.sort {|a,b| b['ctime'] <=> a['ctime']} |
|
|
else |
|
|
c.sort {|a,b| a['ctime'] <=> b['ctime']} |
|
|
end |
|
|
}) |
|
|
|
|
|
comments.remove_thread(50) |
|
|
first_id = comments.insert(50, |
|
|
{'body' => 'First comment at top level','parent_id'=>-1,'ctime'=>1000} |
|
|
) |
|
|
|
|
|
second_id = comments.insert(50, |
|
|
{'body' => 'Second comment at top level','parent_id'=>-1,'ctime'=>1001} |
|
|
) |
|
|
id = comments.insert(50, |
|
|
{'body' => 'reply number one','parent_id'=>second_id,'ctime'=>1002} |
|
|
) |
|
|
id = comments.insert(50, |
|
|
{'body' => 'reply to reply','parent_id'=>id,'ctime'=>1003} |
|
|
) |
|
|
id = comments.insert(50, |
|
|
{'body' => 'reply number two','parent_id'=>second_id,'ctime'=>1002} |
|
|
) |
|
|
rendered_comments = comments.render_comments(50) {|c| |
|
|
puts (" "*c['level']) + c['body'] |
|
|
} |