Commit 81b54db0 by Rocky Duan

major change in urls and naming

parent bc407596
......@@ -27,7 +27,7 @@ namespace :test do
CommentThread.delete_all
Commentable.delete_all
User.delete_all
Feed.delete_all
Notification.delete_all
commentable = Commentable.create!(commentable_type: "questions", commentable_id: "1")
......@@ -67,7 +67,7 @@ namespace :db do
CommentThread.create_indexes
User.create_indexes
Commentable.create_indexes
Feed.create_indexes
Notification.create_indexes
Delayed::Backend::Mongoid::Job.create_indexes
puts "finished"
end
......
......@@ -19,26 +19,17 @@ Mongoid.logger.level = Logger::INFO
Dir[File.dirname(__FILE__) + '/models/*.rb'].each {|file| require file}
# DELETE /api/v1/commentables/:commentable_type/:commentable_id
# delete the commentable object and all of its associated comment threads and comments
delete '/api/v1/:commentable_type/:commentable_id/comments' do |commentable_type, commentable_id|
delete '/api/v1/:commentable_type/:commentable_id/threads' do |commentable_type, commentable_id|
commentable = Commentable.find_or_initialize_by(commentable_type: commentable_type, commentable_id: commentable_id)
commentable.destroy
commentable.to_hash.to_json
end
# GET /api/v1/commentables/:commentable_type/:commentable_id/threads
# get all comment threads associated with a commentable object
# additional parameters accepted: recursive
get '/api/v1/:commentable_type/:commentable_id/threads' do |commentable_type, commentable_id|
commentable = Commentable.find_or_create_by(commentable_type: commentable_type, commentable_id: commentable_id)
commentable.comment_threads.map{|t| t.to_hash(recursive: params["recursive"])}.to_json
end
# POST /api/v1/commentables/:commentable_type/:commentable_id/threads
# create a new comment thread for the commentable object
post '/api/v1/:commentable_type/:commentable_id/threads' do |commentable_type, commentable_id|
commentable = Commentable.find_or_create_by(commentable_type: commentable_type, commentable_id: commentable_id)
thread = commentable.comment_threads.new(params.slice(*%w[title body course_id]))
......@@ -47,26 +38,17 @@ post '/api/v1/:commentable_type/:commentable_id/threads' do |commentable_type, c
thread.to_hash.to_json
end
# GET /api/v1/threads/:thread_id
# get information of a single comment thread
# additional parameters accepted: recursive
get '/api/v1/threads/:thread_id' do |thread_id|
thread = CommentThread.find(thread_id)
thread.to_hash(recursive: params["recursive"]).to_json
end
# PUT /api/v1/threads/:thread_id
# update information of comment thread
put '/api/v1/threads/:thread_id' do |thread_id|
thread = CommentThread.find(thread_id)
thread.update_attributes!(params.slice(*%w[title body]))
thread.to_hash.to_json
end
# POST /api/v1/threads/:thread_id/comments
# create a comment to the comment thread
post '/api/v1/threads/:thread_id/comments' do |thread_id|
thread = CommentThread.find(thread_id)
comment = thread.comments.new(params.slice(*%w[body course_id]))
......@@ -75,36 +57,23 @@ post '/api/v1/threads/:thread_id/comments' do |thread_id|
comment.to_hash.to_json
end
# DELETE /api/v1/threads/:thread_id
# delete the comment thread and its comments
delete '/api/v1/threads/:thread_id' do |thread_id|
thread = CommentThread.find(thread_id)
thread.destroy
thread.to_hash.to_json
end
# GET /api/v1/comments/:comment_id
# retrieve information of a single comment
# additional parameters accepted: recursive
get '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find(comment_id)
comment.to_hash(recursive: params["recursive"]).to_json
end
# PUT /api/v1/comments/:comment_id
# update information of the comment
put '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find(comment_id)
comment.update_attributes!(params.slice(*%w[body endorsed]))
comment.to_hash.to_json
end
# POST /api/v1/comments/:comment_id
# create a sub comment to the comment
post '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find(comment_id)
sub_comment = comment.children.new(params.slice(*%w[body course_id]))
......@@ -113,123 +82,61 @@ post '/api/v1/comments/:comment_id' do |comment_id|
sub_comment.to_hash.to_json
end
# DELETE /api/v1/comments/:comment_id
# delete the comment and its sub comments
delete '/api/v1/comments/:comment_id' do |comment_id|
comment = Comment.find(comment_id)
comment.destroy
comment.to_hash.to_json
end
# PUT /api/v1/votes/comments/:comment_id/users/:user_id
# create or update the vote on the comment
put '/api/v1/votes/comments/:comment_id/users/:user_id' do |comment_id, user_id|
put '/api/v1/comments/:comment_id/votes' do |comment_id|
comment = Comment.find(comment_id)
user = User.find_or_create_by(external_id: user_id)
user.vote(comment, params["value"].intern)
Comment.find(comment_id).to_hash.to_json
handle_vote_for comment
end
# DELETE /api/v1/votes/comments/:comment_id/users/:user_id
# unvote on the comment
delete '/api/v1/votes/comments/:comment_id/users/:user_id' do |comment_id, user_id|
delete '/api/v1/comments/:comment_id/votes' do |comment_id|
comment = Comment.find(comment_id)
user = User.find_or_create_by(external_id: user_id)
user.unvote(comment)
Comment.find(comment_id).to_hash.to_json
handle_unvote_for comment
end
# PUT /api/v1/votes/threads/:thread_id/users/:user_id
# create or update the vote on the comment thread
put '/api/v1/votes/threads/:thread_id/users/:user_id' do |thread_id, user_id|
put '/api/v1/threads/:thread_id/votes' do |thread_id|
thread = CommentThread.find(thread_id)
user = User.find_or_create_by(external_id: user_id)
user.vote(thread, params["value"].intern)
CommentThread.find(thread_id).to_hash.to_json
handle_vote_for thread
end
# DELETE /api/v1/votes/threads/:thread_id/users/:user_id
# unvote on the comment thread
delete '/api/v1/votes/threads/:thread_id/users/:user_id' do |thread_id, user_id|
delete '/api/v1/threads/:thread_id/votes' do |thread_id|
thread = CommentThread.find(thread_id)
user = User.find_or_create_by(external_id: user_id)
user.unvote(thread)
CommentThread.find(thread_id).to_hash.to_json
end
# GET /api/v1/users/:user_id/feeds
# get all subscribed feeds for the user
get '/api/v1/users/:user_id/feeds' do |user_id|
user = User.find_or_create_by(external_id: user_id)
user.subscribed_feeds.map(&:to_hash).to_json
handle_unvote_for thread
end
# POST /api/v1/users/:user_id/follow
# follow user
post '/api/v1/users/:user_id/follow' do |user_id|
user = User.find_or_create_by(external_id: user_id)
followed_user = User.find_or_create_by(external_id: params["follow_user_id"])
user.follow(followed_user)
user.to_hash.to_json
end
# POST /api/v1/users/:user_id/unfollow
# unfollow user
post '/api/v1/users/:user_id/unfollow' do |user_id|
get '/api/v1/users/:user_id/notifications' do |user_id|
user = User.find_or_create_by(external_id: user_id)
followed_user = User.find_or_create_by(external_id: params["follow_user_id"])
user.unfollow(followed_user)
user.to_hash.to_json
user.notifications.map(&:to_hash).to_json
end
# POST /api/v1/users/:user_id/watch/commentable
# watch a commentable
post '/api/v1/users/:user_id/watch/commentable' do |user_id|
post '/api/v1/users/:user_id/subscriptions' do |user_id|
user = User.find_or_create_by(external_id: user_id)
commentable = Commentable.find_or_create_by(commentable_type: params[:commentable_type],
commentable_id: params[:commentable_id])
user.watch_commentable(commentable)
user.to_hash.to_json
end
# POST /api/v1/users/:user_id/unwatch/commentable
# unwatch a commentable
post '/api/v1/users/:user_id/unwatch/commentable' do |user_id|
user = User.find_or_create_by(external_id: user_id)
commentable = Commentable.find_or_create_by(commentable_type: params["commentable_type"],
commentable_id: params["commentable_id"])
user.unwatch_commentable(commentable)
user.to_hash.to_json
end
# POST /api/v1/users/:user_id/watch/thread
# watch a comment thread
post '/api/v1/users/:user_id/watch/thread' do |user_id|
user = User.find_or_create_by(external_id: user_id)
thread = CommentThread.find(params["thread_id"])
user.watch_comment_thread(thread)
user.to_hash.to_json
case params["subscribed_type"]
when "user"
user.follow(User.find_or_create_by(external_id: params["subscribed_id"]))
when "thread"
user.watch_comment_thread(CommentThread.find(params["subscribed_id"]))
else
user.watch_commentable(Commentable.find_or_create_by(commentable_type: params["subscribed_type"], commentable_id: params["subscribed_id"]))
end
user.reload.to_hash.to_json
end
# POST /api/v1/users/:user_id/unwatch/thread
# unwatch a comment thread
post '/api/v1/users/:user_id/unwatch/thread' do |user_id|
delete '/api/v1/users/:user_id/subscriptions' do |user_id|
user = User.find_or_create_by(external_id: user_id)
thread = CommentThread.find(params["thread_id"])
user.unwatch_comment_thread(thread)
user.to_hash.to_json
case params["subscribed_type"]
when "user"
user.unfollow(User.find_or_create_by(external_id: params["subscribed_id"]))
when "thread"
user.unwatch_comment_thread(CommentThread.find(params["subscribed_id"]))
else
user.unwatch_commentable(Commentable.find_or_create_by(commentable_type: params["subscribed_type"], commentable_id: params["subscribed_id"]))
end
user.reload.to_hash.to_json
end
if env.to_s == "development"
......@@ -238,7 +145,19 @@ if env.to_s == "development"
CommentThread.delete_all
Commentable.delete_all
User.delete_all
Feed.delete_all
Notification.delete_all
{}.to_json
end
end
def handle_vote_for(obj)
user = User.find_or_create_by(external_id: params["user_id"])
user.vote(obj, params["value"].to_sym)
obj.reload.to_hash.to_json
end
def handle_unvote_for(obj)
user = User.find_or_create_by(external_id: params["user_id"])
user.unvote(obj)
obj.reload.to_hash.to_json
end
......@@ -53,21 +53,21 @@ class Comment
end
private
def generate_feeds
def generate_notifications
if get_comment_thread.watchers or (author.followers if author)
feed = Feed.new(
feed_type: "post_reply",
notification = Notification.new(
notification_type: "post_reply",
info: {
thread_id: get_comment_thread.id,
thread_title: get_comment_thread.title,
comment_id: id,
},
)
feed.actor = author
feed.target = self
feed.subscribers << (get_comment_thread.watchers + author.followers).uniq_by(&:id)
feed.subscribers.delete(author) if not CommentService.config["send_notifications_to_author"]
feed.save!
notification.actor = author
notification.target = self
notification.receivers << (get_comment_thread.watchers + author.followers).uniq_by(&:id)
notification.receivers.delete(author) if not CommentService.config["send_notifications_to_author"]
notification.save!
end
end
......@@ -78,7 +78,7 @@ private
end
def handle_after_create
generate_feeds
generate_notifications
auto_watch_comment_thread
end
......
......@@ -34,10 +34,10 @@ class CommentThread
end
private
def generate_feeds
def generate_notifications
if watchers or (author.followers if author)
feed = Feed.new(
feed_type: "post_topic",
notification = Notification.new(
notification_type: "post_topic",
info: {
commentable_id: commentable.commentable_id,
commentable_type: commentable.commentable_type,
......@@ -45,11 +45,11 @@ private
thread_title: title,
},
)
feed.actor = author
feed.target = self
feed.subscribers << (commentable.watchers + (author.followers if author).to_a).uniq_by(&:id)
feed.subscribers.delete(author) if not CommentService.config["send_notifications_to_author"] and author
feed.save!
notification.actor = author
notification.target = self
notification.receivers << (commentable.watchers + (author.followers if author).to_a).uniq_by(&:id)
notification.receivers.delete(author) if not CommentService.config["send_notifications_to_author"] and author
notification.save!
end
end
......@@ -60,7 +60,7 @@ private
end
def handle_after_create
generate_feeds
generate_notifications
auto_watch_comment_thread
end
......
class Feed
class Notification
include Mongoid::Document
include Mongoid::Timestamps
field :feed_type, type: String
field :notification_type, type: String
field :info, type: Hash
belongs_to :actor, class_name: "User", inverse_of: :activities, index: true, autosave: true
belongs_to :target, inverse_of: :activities, polymorphic: true, autosave: true
attr_accessible :feed_type, :info
attr_accessible :notification_type, :info
validates_presence_of :feed_type
validates_presence_of :notification_type
if not CommentService.config["allow_anonymity"]
validates_presence_of :actor
end
validates_presence_of :target
has_and_belongs_to_many :subscribers, class_name: "User", inverse_of: :subscribed_feeds, autosave: true
has_and_belongs_to_many :receivers, class_name: "User", inverse_of: :notifications, autosave: true
def to_hash(params={})
as_document.slice(*%w[_id feed_type info actor target])
as_document.slice(*%w[_id notification_type info actor target])
end
end
......@@ -6,8 +6,8 @@ class User
has_many :comments
has_many :comment_threads, inverse_of: :author
has_many :activities, class_name: "Feed", inverse_of: :actor
has_and_belongs_to_many :subscribed_feeds, class_name: "Feed", inverse_of: :subscribers
has_many :activities, class_name: "Notification", inverse_of: :actor
has_and_belongs_to_many :notifications, inverse_of: :receivers
has_and_belongs_to_many :followers, class_name: "User", inverse_of: :followings, autosave: true
has_and_belongs_to_many :followings, class_name: "User", inverse_of: :followers#, autosave: true
......
......@@ -8,12 +8,12 @@ def parse(text)
Yajl::Parser.parse text
end
def init_without_feeds
def init_without_subscriptions
Comment.delete_all
CommentThread.delete_all
Commentable.delete_all
User.delete_all
Feed.delete_all
Notification.delete_all
commentable = Commentable.new(commentable_type: "questions", commentable_id: "1")
commentable.save!
......@@ -72,12 +72,12 @@ def init_without_feeds
end
def init_with_feeds
def init_with_subscriptions
Comment.delete_all
CommentThread.delete_all
Commentable.delete_all
User.delete_all
Feed.delete_all
Notification.delete_all
user1 = User.create!(external_id: "1")
user2 = User.create!(external_id: "2")
......@@ -111,15 +111,15 @@ end
describe "app" do
describe "commentables" do
before(:each) { init_without_feeds }
describe "DELETE /api/v1/commentables/:commentable_type/:commentable_id" do
before(:each) { init_without_subscriptions }
describe "DELETE /api/v1/:commentable_type/:commentable_id/threads" do
it "delete the commentable object and all of its associated comment threads and comments" do
delete '/api/v1/questions/1/comments'
delete '/api/v1/questions/1/threads'
last_response.should be_ok
Commentable.count.should == 0
end
end
describe "GET /api/v1/commentables/:commentable_type/:commentable_id/threads" do
describe "GET /api/v1/:commentable_type/:commentable_id/threads" do
it "get all comment threads associated with a commentable object" do
get "/api/v1/questions/1/threads"
last_response.should be_ok
......@@ -148,7 +148,7 @@ describe "app" do
not_for_me["children"].first["body"].should == "not for me neither!"
end
end
describe "POST /api/v1/commentables/:commentable_type/:commentable_id/threads" do
describe "POST /api/v1/:commentable_type/:commentable_id/threads" do
it "create a new comment thread for the commentable object" do
post '/api/v1/questions/1/threads', title: "Interesting question", body: "cool", course_id: "1", user_id: "1"
last_response.should be_ok
......@@ -158,7 +158,7 @@ describe "app" do
end
end
describe "comment threads" do
before(:each) { init_without_feeds }
before(:each) { init_without_subscriptions }
describe "GET /api/v1/threads/:thread_id" do
it "get information of a single comment thread" do
thread = CommentThread.first
......@@ -195,7 +195,6 @@ describe "app" do
changed_thread.title.should == "new title"
end
end
# POST /api/v1/threads/:thread_id/comments
describe "POST /api/v1/threads/:thread_id/comments" do
it "create a comment to the comment thread" do
thread = CommentThread.first.to_hash(recursive: true)
......@@ -220,7 +219,7 @@ describe "app" do
end
describe "comments" do
before(:each) { init_without_feeds }
before(:each) { init_without_subscriptions }
describe "GET /api/v1/comments/:comment_id" do
it "retrieve information of a single comment" do
comment = Comment.first
......@@ -281,134 +280,124 @@ describe "app" do
end
end
describe "votes" do
before(:each) { init_without_feeds }
describe "PUT /api/v1/votes/comments/:comment_id/users/:user_id" do
before(:each) { init_without_subscriptions }
describe "PUT /api/v1/comments/:comment_id/votes" do
it "create or update the vote on the comment" do
user = User.first
comment = Comment.first
prev_up_votes = comment.up_votes_count
prev_down_votes = comment.down_votes_count
put "/api/v1/votes/comments/#{comment.id}/users/#{user.id}", value: "down"
put "/api/v1/comments/#{comment.id}/votes", user_id: user.id, value: "down"
comment = Comment.find(comment.id)
comment.up_votes_count.should == prev_up_votes - 1
comment.down_votes_count.should == prev_down_votes + 1
end
end
describe "DELETE /api/v1/votes/comments/:comment_id/users/:user_id" do
describe "DELETE /api/v1/comments/:comment_id/votes" do
it "unvote on the comment" do
user = User.first
comment = Comment.first
prev_up_votes = comment.up_votes_count
prev_down_votes = comment.down_votes_count
delete "/api/v1/votes/comments/#{comment.id}/users/#{user.id}"
delete "/api/v1/comments/#{comment.id}/votes", user_id: user.id
comment = Comment.find(comment.id)
comment.up_votes_count.should == prev_up_votes - 1
comment.down_votes_count.should == prev_down_votes
end
end
describe "PUT /api/v1/votes/threads/:thread_id/users/:user_id" do
it "create or update the vote on the comment thread" do
describe "PUT /api/v1/threads/:thread_id/votes" do
it "create or update the vote on the thread" do
user = User.first
thread = CommentThread.first
prev_up_votes = thread.up_votes_count
prev_down_votes = thread.down_votes_count
put "/api/v1/votes/threads/#{thread.id}/users/#{user.id}", value: "down"
put "/api/v1/threads/#{thread.id}/votes", user_id: user.id, value: "down"
thread = CommentThread.find(thread.id)
thread.up_votes_count.should == prev_up_votes - 1
thread.down_votes_count.should == prev_down_votes + 1
end
end
describe "DELETE /api/v1/votes/threads/:thread_id/users/:user_id" do
it "unvote on the comment thread" do
describe "DELETE /api/v1/threads/:thread_id/votes" do
it "unvote on the thread" do
user = User.first
thread = CommentThread.first
prev_up_votes = thread.up_votes_count
prev_down_votes = thread.down_votes_count
delete "/api/v1/votes/threads/#{thread.id}/users/#{user.id}"
delete "/api/v1/threads/#{thread.id}/votes", user_id: user.id
thread = CommentThread.find(thread.id)
thread.up_votes_count.should == prev_up_votes - 1
thread.down_votes_count.should == prev_down_votes
end
end
end
describe "feeds" do
before(:each) { init_with_feeds }
describe "GET /api/v1/users/:user_id/feeds" do
it "get all subscribed feeds on the watched comment threads for the user" do
describe "subscriptions and notifications" do
before(:each) { init_with_subscriptions }
describe "GET /api/v1/users/:user_id/notifications" do
it "get all notifications on the subscribed comment threads for the user" do
user = User.find("1")
get "/api/v1/users/#{user.external_id}/feeds"
get "/api/v1/users/#{user.external_id}/notifications"
last_response.should be_ok
feeds = parse last_response.body
notifications = parse last_response.body
so_easy = Comment.all.select{|c| c.body == "this problem is so easy"}.first
not_for_me_neither = Comment.all.select{|c| c.body == "not for me neither!"}.first
feed_so_easy = feeds.select{|f| f["feed_type"] == "post_reply" and f["info"]["comment_id"] == so_easy.id.to_s}.first
feed_so_easy.should_not be_nil
feed_not_for_me_neither = feeds.select{|f| f["feed_type"] == "post_reply" and f["info"]["comment_id"] == not_for_me_neither.id.to_s}.first
feed_not_for_me_neither.should_not be_nil
notification_so_easy = notifications.select{|f| f["notification_type"] == "post_reply" and f["info"]["comment_id"] == so_easy.id.to_s}.first
notification_so_easy.should_not be_nil
notification_not_for_me_neither = notifications.select{|f| f["notification_type"] == "post_reply" and f["info"]["comment_id"] == not_for_me_neither.id.to_s}.first
notification_not_for_me_neither.should_not be_nil
end
it "get all subscribed feeds on the watched commentable for the user" do
it "get all notifications on the subscribed commentable for the user" do
user = User.find("1")
get "/api/v1/users/#{user.external_id}/feeds"
get "/api/v1/users/#{user.external_id}/notifications"
last_response.should be_ok
feeds = parse last_response.body
feeds.select{|f| f["feed_type"] == "post_topic"}.length.should == 1
problem_wrong = feeds.select{|f| f["feed_type"] == "post_topic"}.first
notifications = parse last_response.body
notifications.select{|f| f["notification_type"] == "post_topic"}.length.should == 1
problem_wrong = notifications.select{|f| f["notification_type"] == "post_topic"}.first
problem_wrong["info"]["thread_title"].should == "This problem is wrong"
end
end
describe "POST /api/v1/users/:user_id/follow" do
describe "POST /api/v1/users/:user_id/subscriptions" do
it "follow user" do
user1 = User.find("1")
user2 = User.find("2")
post "/api/v1/users/#{user1.external_id}/follow", follow_user_id: user2.external_id
post "/api/v1/users/#{user1.external_id}/subscriptions", subscribed_type: "user", subscribed_id: user2.external_id
last_response.should be_ok
User.find("1").followers.length.should == 1
User.find("1").followers.should include user2
User.find("2").followers.length.should == 1
User.find("2").followers.should include user1
end
end
describe "POST /api/v1/users/:user_id/unfollow" do
it "unfollow user" do
user1 = User.find("1")
user2 = User.find("2")
post "/api/v1/users/#{user2.external_id}/unfollow", follow_user_id: user1.external_id
delete "/api/v1/users/#{user2.external_id}/subscriptions", subscribed_type: "user", subscribed_id: user1.external_id
last_response.should be_ok
User.find("1").followers.length.should == 0
end
end
describe "POST /api/v1/users/:user_id/watch/commentable" do
it "watch a commentable" do
it "subscribe a commentable" do
user3 = User.find_or_create_by(external_id: "3")
post "/api/v1/users/#{user3.external_id}/watch/commentable", commentable_type: "questions", commentable_id: "1"
post "/api/v1/users/#{user3.external_id}/subscriptions", subscribed_type: "questions", subscribed_id: "1"
last_response.should be_ok
Commentable.first.watchers.length.should == 3
Commentable.first.watchers.should include user3
end
end
describe "POST /api/v1/users/:user_id/unwatch/commentable" do
it "unwatch a commentable" do
it "unsubscribe a commentable" do
user2 = User.find_or_create_by(external_id: "2")
post "/api/v1/users/#{user2.external_id}/unwatch/commentable", commentable_type: "questions", commentable_id: "1"
delete "/api/v1/users/#{user2.external_id}/subscriptions", subscribed_type: "questions", subscribed_id: "1"
last_response.should be_ok
Commentable.first.watchers.length.should == 1
Commentable.first.watchers.should_not include user2
end
end
describe "POST /api/v1/users/:user_id/watch/thread" do
it "watch a comment thread" do
it "subscribe a comment thread" do
user1 = User.find_or_create_by(external_id: "1")
thread = CommentThread.where(body: "it is unsolvable").first
post "/api/v1/users/#{user1.external_id}/watch/thread", thread_id: thread.id
post "/api/v1/users/#{user1.external_id}/subscriptions", subscribed_type: "thread", subscribed_id: thread.id
last_response.should be_ok
thread = CommentThread.where(body: "it is unsolvable").first
thread.watchers.length.should == 2
thread.watchers.should include user1
end
end
describe "POST /api/v1/users/:user_id/unwatch/thread" do
it "unwatch a comment thread" do
it "unsubscribe a comment thread" do
user2 = User.find_or_create_by(external_id: "2")
thread = CommentThread.where(body: "it is unsolvable").first
post "/api/v1/users/#{user2.external_id}/unwatch/thread", thread_id: thread.id
delete "/api/v1/users/#{user2.external_id}/subscriptions", subscribed_type: "thread", subscribed_id: thread.id
last_response.should be_ok
thread = CommentThread.where(body: "it is unsolvable").first
thread.watchers.length.should == 0
......
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