Skip to content

Instantly share code, notes, and snippets.

@Loupi
Created May 22, 2012 01:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Loupi/2766062 to your computer and use it in GitHub Desktop.
Save Loupi/2766062 to your computer and use it in GitHub Desktop.
Redis Nested Comments in Ruby
require 'redis'
require 'json'
class CommentsRepository
def initialize
@redis = Redis.new
end
def save(itemId, comment, parentId = nil)
now = Time.now.utc.to_i
id = @redis.incr(1)
comment = {id: id, time: now}.merge(comment)
k = parentId ? sub_comments_key(parentId) : comments_key(itemId)
@redis.multi { |multi|
multi.del json_key(itemId)
multi.hmset comment_key(id), comment.map { |k,v| [k,v] }.flatten(1)
multi.zadd k, now, id
}
id
end
def get(id)
jsonId = json_key(id)
jsonComments = @redis.get(jsonId)
if jsonComments
comments = JSON.parse(jsonComments)
else
comments = multi_fetch(comments_key(id))
@redis.set jsonId, comments.to_json
end
comments
end
def leaf(id)
comment = @redis.hgetall(comment_key(id))
comment['comments'] = multi_fetch(sub_comments_key(id))
comment
end
def delete(comment, parentId, itemId, isMainThread = true)
@redis.multi { |multi|
inner_delete comment, parentId, isMainThread, multi
multi.del json_key(itemId)
}
end
private
def multi_fetch(key)
commentIds = @redis.zrange(key, 0, -1)
comments = @redis.multi { |multi|
commentIds.map { |id| multi.hgetall(comment_key(id)) }
}
process_comments(comments)
end
def process_comments(comments)
comments.each { |comment|
subCommentsId = sub_comments_key(comment['id'])
if @redis.zcard(subCommentsId) > 0
comment['comments'] = multi_fetch(subCommentsId)
end
}
end
def inner_delete(comment, parentId, isMainThread, multi)
if comment.kind_of?(Array)
comment.each {|c| inner_delete c, parentId, isMainThread, multi}
else
id = comment['id']
comments = comment['comments']
k = isMainThread ? comments_key(parentId) : sub_comments_key(parentId)
multi.del comment_key(id)
multi.zrem k, id
inner_delete comments, id, false, multi if comments
end
end
def json_key(id)
"comment:#{id}:json"
end
def comment_key(id)
"comment:#{id}"
end
def comments_key(id)
"comments:#{id}"
end
def sub_comments_key(id)
"comment:#{id}:sub"
end
end
require 'optparse'
require './nested-redis'
options = {}
COMMANDS = {save: 1, delete: 2, print: 3, leaf: 4, remove: 5, dummy: 6}
FORMATS = {text: 1, json: 2, pretty_json:3}
optparse = OptionParser.new { |opts|
opts.banner = "Usage: redis-cmd.rb command [options]"
opts.on('-s', '--save item, parent, comment', Array,
'Save a comment on an item.') { |itemId, parentId, comment|
options[:command] = COMMANDS[:save]
options[:itemId] = itemId
options[:parentId] = parentId if parentId != '0'
options[:comment] = comment
}
opts.on('-p', '--print itemId',
'Print the comment hierarchy of an item.') { |itemId|
options[:command] = COMMANDS[:print]
options[:itemId] = itemId
options[:format] = FORMATS[:text]
}
opts.on('-l', '--leaf leafId',
'Print the hierarchy of a leaf.') { |leafId|
options[:command] = COMMANDS[:leaf]
options[:itemId] = leafId
options[:format] = FORMATS[:text]
}
opts.on('-d', '--delete itemId',
'Delete all the comments of an item.') { |itemId|
options[:command] = COMMANDS[:delete]
options[:itemId] = itemId
}
opts.on('-r', '--remove item, leaf, parent', Array,
'Delete a leaf and it\'s hierarchy from a comment.') { |itemId, leafId, parentId|
options[:command] = COMMANDS[:remove]
options[:itemId] = itemId
options[:leafId] = leafId
options[:parentId] = parentId
}
opts.on('-u', '--dummy itemId',
'Save a dummy comment hierarchy on an item.') { |itemId|
options[:command] = COMMANDS[:dummy]
options[:itemId] = itemId
}
opts.on('-j', '--json [pretty]', 'Set the output format to JSON.') { |pretty|
options[:format] = pretty ? FORMATS[:pretty_json] : FORMATS[:json]
}
opts.on( '-h', '--help', 'Display this screen' ) {
puts opts
exit
}
}
def print_comments(comments, tabs = 0)
comments.each { |comment|
tabs.times { putc "\t" }
puts "#{comment['comment']}"
if comment['comments']
print_comments comment['comments'], tabs + 1
end
}
end
def save_dummy(cr, id)
tree = cr.save(id, { comment:'Tree', test:1 })
root = cr.save(id, { comment:'Root', kind:'ginger', age:50 }, tree)
trunk = cr.save(id, { comment:'Trunk', kmh_to_mph:0.621 }, tree)
branch = cr.save(id, { comment:'Branch' }, trunk)
leaf = cr.save(id, { comment:'Leaf' }, branch)
top = cr.save(id, { comment:'Top' }, tree)
end
def print_result comments, options
case options[:format]
when FORMATS[:text]
print_comments comments
when FORMATS[:json]
puts comments.to_json
when FORMATS[:pretty_json]
puts JSON.pretty_generate(comments)
end
end
optparse.parse!
id = options[:itemId]
cr = CommentsRepository.new
case options[:command]
when COMMANDS[:save]
comment = { comment: options[:comment] }
id = cr.save(id, comment, options[:parentId])
puts "New comment saved with id #{id}."
when COMMANDS[:print]
print_result cr.get(id), options
when COMMANDS[:leaf]
print_result [cr.leaf(id)], options
when COMMANDS[:delete]
cr.delete cr.get(id), id, id
when COMMANDS[:remove]
cr.delete cr.leaf(options[:leafId]), options[:parentId], id, false
when COMMANDS[:dummy]
save_dummy cr, id
else
puts optparse
end
@Loupi
Copy link
Author

Loupi commented May 27, 2012

Example usage:

ruby redis-cmd.rb -u 2
ruby redis-cmd.rb -p 2 -j
ruby redis-cmd.rb -s 1,0,"Comment #1"
ruby redis-cmd.rb -s 1,0,"Comment #2"
ruby redis-cmd.rb -p 1
ruby redis-cmd.rb -s 1,1,"Sub comment #1"
ruby redis-cmd.rb -p 1 -j 1
ruby redis-cmd.rb -r 1,3,1
ruby redis-cmd.rb -p 1 -j 1
ruby redis-cmd.rb -d 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment