Commit a7918b01 by Rocky Duan

initial import

parents
*.gem
*.rbc
.bundle
.config
coverage
InstalledFiles
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
# YARD artifacts
.yardoc
_yardoc
doc/
Gemfile.lock
*.sqlite3
.*.swp
.*.swo
.*.swm
rvm use 1.9.3
source :rubygems
gem 'sinatra'
gem 'activerecord'
gem 'yajl-ruby'
gem 'ancestry'
gem 'sqlite3'
gem 'ampex'
group :test do
gem 'rspec'
gem 'rack-test', :require => "rack/test"
end
comment_as_a_service
====================
An independent comment system which supports voting and nested comments. It also supports features including instructor endorsement for education-aimed discussion platforms.
require 'rubygems'
require 'active_record'
require 'yaml'
require 'logger'
desc "Load the environment"
task :environment do
env = ENV["SINATRA_ENV"] || "development"
databases = YAML.load_file("config/database.yml")
ActiveRecord::Base.establish_connection(databases[env])
end
namespace :db do
desc "Migrate the database"
task :migrate => :environment do
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Migration.verbose = true
ActiveRecord::Migrator.migrate("db/migrate")
end
task :seed => :environment do
require_relative 'models/comment.rb'
require_relative 'models/comment_thread.rb'
require_relative 'models/vote.rb'
Comment.delete_all
CommentThread.delete_all
Vote.delete_all
comment_thread = CommentThread.create! :commentable_type => "questions", :commentable_id => 1
5.times do
comment_thread.root_comments.create :body => "top comment", :title => "top #{rand(10)}", :user_id => 1, :course_id => 1, :comment_thread_id => comment_thread.id
end
50.times do
Comment.all.reject{|c| c.is_root?}.sample.children.create :body => "comment body", :title => "comment title #{rand(50)}", :user_id => 1, :course_id => 1, :comment_thread_id => comment_thread.id
end
Comment.all.reject{|c| c.is_root?}.each do |c|
(1..20).each do |id|
Vote.create! :value => ["up", "down"].sample, :comment_id => c.id, :user_id => id
end
end
end
end
require 'rubygems'
require 'yajl'
require 'active_record'
require 'sinatra'
require_relative 'models/comment'
require_relative 'models/comment_thread'
require_relative 'models/vote'
env_index = ARGV.index("-e")
env_arg = ARGV[env_index + 1] if env_index
env = env_arg || ENV["SINATRA_ENV"] || "development"
databases = YAML.load_file("config/database.yml")
ActiveRecord::Base.establish_connection(databases[env])
# retrive all comments of a commentable object
get '/api/v1/commentables/:commentable_type/:commentable_id/comments' do |commentable_type, commentable_id|
comment_thread = CommentThread.find_or_create_by_commentable_type_and_commentable_id(commentable_type, commentable_id)
comment_thread.json_comments
end
# create a new top-level comment
post '/api/v1/commentables/:commentable_type/:commentable_id/comments' do |commentable_type, commentable_id|
comment_thread = CommentThread.find_or_create_by_commentable_type_and_commentable_id(commentable_type, commentable_id)
comment_params = params.select {|key, value| %w{body title user_id course_id}.include? key}
comment_params.merge :comment_thread_id => comment_thread.id
comment = comment_thread.root_comments.create(comment_params)
if comment.valid?
comment.to_json
else
error 400, comment.errors.to_json
end
end
# delete a commentable object and its associated comments
delete '/api/v1/commentables/:commentable_type/:commentable_id' do |commentable_type, commentable_id|
comment_thread = CommentThread.find_by_commentable_type_and_commentable_id(commentable_type, commentable_id)
if comment_thread.nil?
error 400, {:error => "commentable object does not exist"}.to_json
else
comment_thread.destroy
comment_thread.to_json
end
end
# create a new subcomment (reply to comment) only if the comment is NOT a super comment
post '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find_by_id(comment_id)
if comment.nil? or comment.is_root?
error 400, {:error => "invalid comment id"}.to_json
else
comment_params = params.select {|key, value| %w{body title user_id course_id}.include? key}
comment_params.merge :comment_thread_id => comment.root.comment_thread.id
sub_comment = comment.children.create(comment_params)
if comment.valid?
comment.to_json
else
error 400, comment.errors.to_json
end
end
end
# delete the comment and the associated sub comments only if the comment is NOT the super comment
delete '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find_by_id(comment_id)
if comment.nil? or comment.is_root?
error 400, {:error => "invalid comment id"}.to_json
else
comment.destroy
comment.to_json
end
end
# update the body / title (or both) of a comment provided the comment is NOT the super comment
put '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find_by_id(comment_id)
if comment.nil? or comment.is_root?
error 400, {:error => "invalid comment id"}.to_json
else
comment_params = params.select {|key, value| %w{body title}.include? key}
if comment.update_attributes(comment_params)
comment.to_json
else
error 400, comment.errors.to_json
end
end
end
# create or update the vote on the comment by the user
put '/api/v1/votes/comments/:comment_id/users/:user_id' do |comment_id, user_id|
if not %w{up down}.include? params["value"]
error 400, {:error => "value must be up or down"}.to_json
else
comment = Comment.find_by_id(comment_id)
if comment.nil?
error 400, {:error => "invalid comment id"}.to_json
else
vote = Vote.create_or_update :user_id => user_id, :comment_id => comment_id, :value => params["value"]
if vote
vote.to_json
else
error 400, vote.errors.to_json
end
end
end
end
# undo the vote on the comment by the user
delete '/api/v1/votes/comments/:comment_id/users/:user_id' do |comment_id, user_id|
vote = Vote.find_by_comment_id_and_user_id(comment_id, user_id)
if vote.nil?
error 400, {:error => "vote does not exist"}.to_json
else
vote.destroy
vote.to_json
end
end
if env.to_s == "development"
get '/api/v1/clean' do
Comment.delete_all
CommentThread.delete_all
Vote.delete_all
{}.to_json
end
end
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 100
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 100
# Taken and modified from gem 'acts_as_commentable_with_threading':
# https://github.com/elight/acts_as_commentable_with_threading/blob/master/lib/generators/acts_as_commentable_with_threading_migration/templates/migration.rb
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.text :body
t.text :title, :default => ""
t.string :ancestry
t.integer :user_id
t.integer :course_id
t.integer :comment_thread_id
t.timestamps
end
add_index :comments, :user_id
add_index :comments, :course_id
add_index :comments, :ancestry
end
def self.down
drop_table :comments
end
end
class CreateCommentThreads < ActiveRecord::Migration
def self.up
create_table :comment_threads do |t|
t.string :commentable_type
t.string :commentable_id
t.timestamps
end
end
def self.down
drop_table :comment_threads
end
end
class CreateVotes < ActiveRecord::Migration
def self.up
create_table :votes do |t|
t.integer :user_id
t.integer :comment_id
t.string :value
t.timestamps
end
add_index :votes, :comment_id
add_index :votes, :user_id
add_index :votes, [:comment_id, :user_id]
end
def self.down
drop_table :votes
end
end
require 'active_record'
require 'ancestry'
class Comment < ActiveRecord::Base
attr_accessible :body, :title, :user_id, :course_id, :comment_thread_id
has_ancestry
has_many :votes
belongs_to :comment_thread
def self.hash_tree(nodes)
nodes.map do |node, sub_nodes|
{
:id => node.id,
:body => node.body,
:title => node.title,
:user_id => node.user_id,
:course_id => node.course_id,
:created_at => node.created_at,
:updated_at => node.updated_at,
:comment_thread_id => node.comment_thread_id,
:children => hash_tree(sub_nodes).compact,
:votes => {:up => Vote.comment_id(node.id).up.count, :down => Vote.comment_id(node.id).down.count},
}
end
end
def to_hash_tree
self.class.hash_tree(self.subtree.arrange)
end
end
require 'active_record'
class CommentThread < ActiveRecord::Base
has_one :super_comment, :class_name => "Comment", :dependent => :destroy
# Ensures that each thread is associated with a commentable object
validates_presence_of :commentable_type, :commentable_id
# Ensures that there is only one thread for each commentable object
validates_uniqueness_of :commentable_id, :scope => :commentable_type
# Helper class method to create a new thread with the corresponding super comment
#def self.find_or_build(commentable_type, commentable_id)
# comment_thread = CommentThread.find_or_create_by_commentable_type_and_commentable
# Create a super comment which does not hold anything itself, but points to all comments of the thread
after_create :create_super_comment
def create_super_comment
comment = Comment.create! :comment_thread_id => self.id
end
def root_comments
super_comment.children
end
def comments
super_comment.descendants
end
def json_comments
super_comment.to_hash_tree.first[:children].to_json
end
end
require 'active_record'
# Adapted from "Service-Oriented Design with Ruby and Rails"
class Vote < ActiveRecord::Base
attr_accessible :value, :user_id, :comment_id
belongs_to :comment
validates_inclusion_of :value, :in => %w{up down}
validates_uniqueness_of :user_id, :scope => :comment_id
validates_presence_of :comment_id, :user_id
scope :up, :conditions => ["value = ?", "up"]
scope :down, :conditions => ["value = ?", "down"]
scope :user_id, lambda {|user_id| {:conditions => ["user_id = ?", user_id]}}
scope :comment_id, lambda {|comment_id| {:conditions => ["comment_id = ?", comment_id]}}
def self.create_or_update(attributes)
vote = Vote.find_by_comment_id_and_user_id(attributes[:comment_id], attributes[:user_id])
if vote
vote.value = attributes[:value]
vote.save
vote
else
Vote.create(attributes)
end
end
end
require File.join(File.dirname(__FILE__), '..', 'app')
require 'sinatra'
require 'rack/test'
# setup test environment
set :environment, :test
set :run, false
set :raise_errors, true
set :logging, false
def app
Sinatra::Application
end
RSpec.configure do |config|
config.include Rack::Test::Methods
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment