Merge branch 'like-comments'

This commit is contained in:
Raphael Sofaer 2011-07-11 16:18:19 -07:00
commit be5f3bf95e
41 changed files with 374 additions and 231 deletions

View file

@ -31,8 +31,6 @@ class AdminsController < ApplicationController
def stats
@popular_tags = ActsAsTaggableOn::Tagging.joins(:tag).limit(15).count(:group => :tag, :order => 'count(taggings.id) DESC')
@most_liked_posts = Post.where(:type => ['StatusMessage', 'ActivityStreams::Photo'],
:public => true).order('likes_count DESC').limit(15).all
@new_posts = Post.where(:type => ['StatusMessage','ActivityStreams::Photo'],
:public => true).order('created_at DESC').limit(15).all
end

View file

@ -9,17 +9,16 @@ class LikesController < ApplicationController
respond_to :html, :mobile, :json
def create
target = current_user.find_visible_post_by_id params[:post_id]
positive = (params[:positive] == 'true') ? true : false
if target
@like = current_user.build_like(:positive => positive, :post => target)
@like = current_user.build_like(:positive => positive, :target => target)
if @like.save
Rails.logger.info("event=create type=like user=#{current_user.diaspora_handle} status=success like=#{@like.id} positive=#{positive}")
Postzord::Dispatch.new(current_user, @like).post
respond_to do |format|
format.js { render :status => 201 }
format.js { render 'likes/update', :status => 201 }
format.html { render :nothing => true, :status => 201 }
format.mobile { redirect_to post_path(@like.post_id) }
end
@ -32,8 +31,12 @@ class LikesController < ApplicationController
end
def destroy
if @like = Like.where(:id => params[:id], :author_id => current_user.person.id, :post_id => params[:post_id]).first
if @like = Like.where(:id => params[:id], :author_id => current_user.person.id).first
current_user.retract(@like)
respond_to do |format|
format.all{}
format.js{ render 'likes/update' }
end
else
respond_to do |format|
format.mobile {redirect_to :back}
@ -43,11 +46,21 @@ class LikesController < ApplicationController
end
def index
if target = current_user.find_visible_post_by_id(params[:post_id])
if target
@likes = target.likes.includes(:author => :profile)
render :layout => false
else
render :nothing => true, :status => 404
end
end
def target
@target ||= if params[:post_id]
current_user.find_visible_post_by_id(params[:post_id])
else
comment = Comment.find(params[:comment_id])
comment = nil unless current_user.find_visible_post_by_id(comment.post_id)
comment
end
end
end

View file

@ -4,7 +4,7 @@
class SessionsController < Devise::SessionsController
after_filter :enqueue_update, :only => :create
#after_filter :enqueue_update, :only => :create
protected
def enqueue_update

View file

@ -8,11 +8,25 @@ module LikesHelper
links.join(", ").html_safe
end
def like_action(post, current_user=current_user)
if current_user.liked?(post)
link_to t('shared.stream_element.unlike'), post_like_path(post, current_user.like_for(post)), :method => :delete, :class => 'unlike', :remote => true
def like_action(target, current_user=current_user)
target = target.model if target.instance_of?(PostsFake::Fake)
if target.instance_of?(Comment)
if current_user.liked?(target)
link_to t('shared.stream_element.unlike'), comment_like_path(target, current_user.like_for(target)), :method => :delete, :class => 'unlike', :remote => true
else
link_to t('shared.stream_element.like'), comment_likes_path(target, :positive => 'true'), :method => :post, :class => 'like', :remote => true
end
else
link_to t('shared.stream_element.like'), post_likes_path(post, :positive => 'true'), :method => :post, :class => 'like', :remote => true
if current_user.liked?(target)
link_to t('shared.stream_element.unlike'), post_like_path(target, current_user.like_for(target)), :method => :delete, :class => 'unlike', :remote => true
else
link_to t('shared.stream_element.like'), post_likes_path(target, :positive => 'true'), :method => :post, :class => 'like', :remote => true
end
end
end
end

View file

@ -14,6 +14,7 @@ class Comment < ActiveRecord::Base
include Diaspora::Socketable
include Diaspora::Taggable
include Diaspora::Likeable
acts_as_taggable_on :tags
extract_tags_from :text

View file

@ -8,18 +8,27 @@ class Like < ActiveRecord::Base
include Diaspora::Webhooks
include Diaspora::Guid
include Diaspora::Relayable
xml_attr :target_type
include Diaspora::Relayable
include Diaspora::Socketable
xml_attr :positive
xml_attr :diaspora_handle
belongs_to :post, :counter_cache => true
belongs_to :target, :polymorphic => true
belongs_to :author, :class_name => 'Person'
validates_uniqueness_of :post_id, :scope => :author_id
validates_presence_of :author, :post
validates_uniqueness_of :target_id, :scope => [:target_type, :author_id]
validates_presence_of :author, :target
after_create do
self.target.update_likes_counter
end
after_destroy do
self.target.update_likes_counter
end
def diaspora_handle
self.author.diaspora_handle
@ -30,18 +39,20 @@ class Like < ActiveRecord::Base
end
def parent_class
Post
self.target_type.constantize
end
def parent
self.post
self.target
end
def parent= parent
self.post = parent
self.target = parent
end
def notification_type(user, person)
Notifications::Liked if self.post.author == user.person && user.person != person
#TODO(dan) need to have a notification for likes on comments, until then, return nil
return nil if self.target_type == "Comment"
Notifications::Liked if self.target.author == user.person && user.person != person
end
end

View file

@ -19,7 +19,7 @@ class Notification < ActiveRecord::Base
if target.respond_to? :notification_type
if note_type = target.notification_type(recipient, actor)
if(target.is_a? Comment) || (target.is_a? Like)
n = note_type.concatenate_or_create(recipient, target.post, actor, note_type)
n = note_type.concatenate_or_create(recipient, target.parent, actor, note_type)
else
n = note_type.make_notification(recipient, target, actor, note_type)
end

View file

@ -9,13 +9,13 @@ class Post < ActiveRecord::Base
include Diaspora::Webhooks
include Diaspora::Guid
include Diaspora::Likeable
xml_attr :diaspora_handle
xml_attr :public
xml_attr :created_at
has_many :comments, :dependent => :destroy
has_many :likes, :conditions => {:positive => true}, :dependent => :delete_all
has_many :dislikes, :conditions => {:positive => false}, :class_name => 'Like', :dependent => :delete_all
has_many :aspect_visibilities
has_many :aspects, :through => :aspect_visibilities

View file

@ -24,7 +24,6 @@ class StatusMessage < Post
serialize :youtube_titles, Hash
before_create :build_tags
after_create :create_mentions
def text(opts = {})

View file

@ -183,26 +183,26 @@ class User < ActiveRecord::Base
# Check whether the user has liked a post. Extremely inefficient if the post's likes are not loaded.
# @param [Post] post
def liked?(post)
if post.likes.loaded?
if self.like_for(post)
def liked?(target)
if target.likes.loaded?
if self.like_for(target)
return true
else
return false
end
else
Like.exists?(:author_id => self.person.id, :post_id => post.id)
Like.exists?(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id)
end
end
# Get the user's like of a post, if there is one. Extremely inefficient if the post's likes are not loaded.
# @param [Post] post
# @return [Like]
def like_for(post)
if post.likes.loaded?
return post.likes.detect{ |like| like.author_id == self.person.id }
def like_for(target)
if target.likes.loaded?
return target.likes.detect{ |like| like.author_id == self.person.id }
else
return Like.where(:author_id => self.person.id, :post_id => post.id).first
return Like.where(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id).first
end
end
@ -215,13 +215,11 @@ class User < ActiveRecord::Base
end
######### Posts and Such ###############
def retract(post)
if post.respond_to?(:relayable?) && post.relayable?
aspects = post.parent.aspects
retraction = RelayableRetraction.build(self, post)
def retract(target)
if target.respond_to?(:relayable?) && target.relayable?
retraction = RelayableRetraction.build(self, target)
else
aspects = post.aspects
retraction = Retraction.for(post)
retraction = Retraction.for(target)
end
mailman = Postzord::Dispatch.new(self, retraction)

View file

@ -2,7 +2,7 @@
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
%li.comment.posted{:data=>{:guid => comment.id}, :class => ("hidden" if(defined? hidden))}
%li.comment.posted{:id => comment.guid, :class => ("hidden" if(defined? hidden))}
- if current_user && (current_user.owns?(comment) || current_user.owns?(post))
.right.controls
= link_to image_tag('deletelabel.png'), post_comment_path(comment.post_id, comment), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete comment_delete", :title => t('delete')
@ -16,7 +16,18 @@
%span{:class => direction_for(comment.text)}
= markdownify(Diaspora::Taggable.format_tags(comment.text), :youtube_maps => comment.youtube_titles)
%br
.comment_info
%time.timeago{:datetime => comment.created_at}
= comment.created_at ? timeago(comment.created_at) : timeago(Time.now)
·
.likes
= render "likes/likes_container", :target_id => comment.id, :likes_count => comment.likes_count
- if comment.likes_count > 0
·
- unless (defined?(@commenting_disabled) && @commenting_disabled)
%span.like_action
= like_action(comment, current_user)

View file

@ -5,7 +5,7 @@
- unless comments_expanded
%ul.show_comments{:class => ("hidden" if post.comments.size <= 3)}
%li
%b= comment_toggle( post)
= comment_toggle( post)
%ul.comments{:id => post.id, :class => ('loaded' if post.comments.size <= 3)}
-if post.comments.size > 3 && !comments_expanded
@ -14,5 +14,5 @@
= render :partial => 'comments/comment', :collection => post.comments, :locals => {:post => post}
- unless @commenting_disabled
.new_comment_form_wrapper{:class => ( 'hidden' if post.comments.size == 0)}
.new_comment_form_wrapper{:class => ('hidden' if post.comments.size == 0)}
= new_comment_form(post.id, current_user)

View file

@ -1,5 +1,5 @@
WebSocketReceiver.processComment(<%= @comment.post_id %>,
<%= @comment.id %>,
WebSocketReceiver.processComment("<%= @comment.post.guid %>",
"<%= @comment.guid%>",
"<%= escape_javascript(render(:partial => 'comments/comment', :locals => { :comment => @comment, :person => current_user.person}))%>",
false);

View file

@ -2,11 +2,13 @@
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
- if likes_count > 0
.likes_container
.likes
= image_tag('icons/heart.svg')
= link_to t('likes.likes.people_like_this', :count => likes_count), post_likes_path(post_id), :class => "expand_likes"
%span.hidden.likes_list
/= render 'likes/likes', :likes => likes
.likes_container
- if likes_count > 0
= image_tag('icons/heart.svg')
- if defined?(likes_index_link) && likes_index_link
= link_to t('likes.likes.people_like_this', :count => likes_count), post_likes_path(target_id), :class => "expand_likes"
- else
= t('likes.likes.people_like_this', :count => likes_count)
%span.hidden.likes_list
/= render 'likes/likes', :likes => likes

View file

@ -1,4 +0,0 @@
$(".like_action", ".stream_element[data-guid=<%=@like.post_id%>]").html("<%= escape_javascript(like_action(@like.post))%>");
WebSocketReceiver.processLike("<%=@like.post_id%>", "<%= escape_javascript(render("likes/likes_container", :post_id => @like.post_id, :likes_count => @like.post.likes.count)) %>");

View file

@ -1,3 +0,0 @@
$(".like_action", ".stream_element[data-guid=<%=@like.post_id%>]").html("<%= escape_javascript(like_action(@like.post))%>");
WebSocketReceiver.processLike("<%=@like.post_id%>", "<%= escape_javascript(render("likes/likes_container", :post_id => @like.post_id, :likes_count => @like.post.likes.count)) %>");

View file

@ -0,0 +1,2 @@
$(".like_action", "#<%=@like.target.guid%>").first().html("<%= escape_javascript(like_action(@like.target))%>");
WebSocketReceiver.processLike("<%=@like.target.guid%>", "<%= escape_javascript(render("likes/likes_container", :target_id => @like.target_id, :likes_count => @like.target.likes_count)) %>");

View file

@ -4,12 +4,11 @@
= t('.liked', :name => "#{@sender.name} (#{@sender.diaspora_handle})")
%p
= @like.post.formatted_message(:plain_text => true)
= @like.target.formatted_message(:plain_text => true)
%p
%br
= link_to t('.sign_in'), post_url(@like.post)
= link_to t('.sign_in'), post_url(@like.target)
%br
= t('notifier.love')
%br

View file

@ -2,7 +2,7 @@
!= t('notifier.liked.liked', :name => "#{@sender.name} (#{@sender.diaspora_handle})")
!= @like.post.formatted_message(:plain_text => true)
!= @like.target.formatted_message(:plain_text => true)
!= t('notifier.love')
!= t('notifier.diaspora')

View file

@ -1,3 +1,3 @@
var target = $(".stream_element[data-guid=<%= escape_javascript(@post.id.to_s) %>]")
var target = $("#<%= @post.guid %>")
target.find(".sm_body").toggleClass("hidden");
target.find(".undo_text").toggleClass("hidden");

View file

@ -1,2 +1,2 @@
var target = $(".stream_element[data-guid=<%= escape_javascript(@post.id.to_s) %>]")
var target = $("#<%= @post.guid %>")
target.hide('blind', { direction: 'vertical' }, 300, function(){ target.remove() });

View file

@ -2,7 +2,7 @@
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
.stream_element{:data=>{:guid=>post.id}}
.stream_element{:id => post.guid}
- if current_user && post.author.owner_id == current_user.id
.right.controls
= link_to image_tag('deletelabel.png'), post_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete stream_element_delete", :title => t('delete')
@ -55,8 +55,7 @@
·
= link_to t('comments.new_comment.comment'), '#', :class => 'focus_comment_textarea'
.likes
- if post.likes_count > 0
= render "likes/likes_container", :post_id => post.id, :likes_count => post.likes_count, :current_user => current_user
.likes.on_post
= render "likes/likes_container", :target_id => post.id, :likes_count => post.likes_count, :current_user => current_user, :likes_index_link => true
= render "comments/comments", :post => post, :current_user => current_user, :commenting_disabled => (defined?(@commenting_disabled) && @commenting_disabled)

View file

@ -8,4 +8,4 @@
:all_aspects => current_user.aspects
}
),
:post_id => @status_message.id}.to_json.html_safe%>
:post_id => @status_message.guid}.to_json.html_safe%>

View file

@ -19,6 +19,12 @@ Diaspora::Application.routes.draw do
resources :comments, :only => [:create, :destroy, :index]
end
# roll up likes into a nested resource above
resources :comments, :only => [:create, :destroy] do
resources :likes, :only => [:create, :destroy, :index]
end
get 'bookmarklet' => 'status_messages#bookmarklet'
get 'p/:id' => 'publics#post', :as => 'public_post'

View file

@ -0,0 +1,37 @@
class LikesOnComments < ActiveRecord::Migration
class Likes < ActiveRecord::Base; end
def self.up
remove_foreign_key :likes, :posts
add_column :likes, :target_type, :string, :limit => 60, :null => false
rename_column :likes, :post_id, :target_id
add_column :comments, :likes_count, :integer, :default => 0, :null => false
execute <<SQL
UPDATE likes
SET target_type = 'Post'
SQL
execute <<SQL
UPDATE posts
SET likes_count = (SELECT COUNT(*) FROM likes WHERE likes.target_id = posts.id AND likes.target_type = 'Post')
SQL
#There are some duplicate likes.
keeper_likes = Like.group(:target_id, :author_id, :target_type).having('COUNT(*) > 1')
keeper_likes.each do |like|
l = Like.arel_table
Like.where(:target_id => like.target_id, :author_id => like.author_id, :target_type => like.target_type).where(l[:id].not_eq(like.id)).delete_all
end
add_index :likes, [:target_id, :author_id, :target_type], :unique => true
end
def self.down
remove_column :comments, :likes_count
remove_column :likes, :target_type
rename_column :likes, :target_id, :post_id
add_index :likes, :post_id
remove_index :likes, :target_id
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20110707221112) do
ActiveRecord::Schema.define(:version => 20110707234802) do
create_table "aspect_memberships", :force => true do |t|
t.integer "aspect_id", :null => false
@ -46,15 +46,16 @@ ActiveRecord::Schema.define(:version => 20110707221112) do
add_index "aspects", ["user_id"], :name => "index_aspects_on_user_id"
create_table "comments", :force => true do |t|
t.text "text", :null => false
t.integer "post_id", :null => false
t.integer "author_id", :null => false
t.string "guid", :null => false
t.text "text", :null => false
t.integer "post_id", :null => false
t.integer "author_id", :null => false
t.string "guid", :null => false
t.text "author_signature"
t.text "parent_author_signature"
t.text "youtube_titles"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "likes_count", :default => 0, :null => false
end
add_index "comments", ["author_id"], :name => "index_comments_on_person_id"
@ -109,19 +110,21 @@ ActiveRecord::Schema.define(:version => 20110707221112) do
add_index "invitations", ["sender_id"], :name => "index_invitations_on_sender_id"
create_table "likes", :force => true do |t|
t.boolean "positive", :default => true
t.integer "post_id"
t.boolean "positive", :default => true
t.integer "target_id"
t.integer "author_id"
t.string "guid"
t.text "author_signature"
t.text "parent_author_signature"
t.datetime "created_at"
t.datetime "updated_at"
t.string "target_type", :limit => 60, :null => false
end
add_index "likes", ["author_id"], :name => "likes_author_id_fk"
add_index "likes", ["guid"], :name => "index_likes_on_guid", :unique => true
add_index "likes", ["post_id"], :name => "index_likes_on_post_id"
add_index "likes", ["target_id", "author_id", "target_type"], :name => "index_likes_on_target_id_and_author_id_and_target_type", :unique => true
add_index "likes", ["target_id"], :name => "index_likes_on_post_id"
create_table "mentions", :force => true do |t|
t.integer "post_id", :null => false
@ -418,7 +421,6 @@ ActiveRecord::Schema.define(:version => 20110707221112) do
add_foreign_key "invitations", "users", :name => "invitations_sender_id_fk", :column => "sender_id", :dependent => :delete
add_foreign_key "likes", "people", :name => "likes_author_id_fk", :column => "author_id", :dependent => :delete
add_foreign_key "likes", "posts", :name => "likes_post_id_fk", :dependent => :delete
add_foreign_key "messages", "conversations", :name => "messages_conversation_id_fk", :dependent => :delete
add_foreign_key "messages", "people", :name => "messages_author_id_fk", :column => "author_id", :dependent => :delete

20
lib/diaspora/likeable.rb Normal file
View file

@ -0,0 +1,20 @@
# Copyright (c) 2010, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module Diaspora
module Likeable
def self.included(model)
model.instance_eval do
has_many :likes, :conditions => {:positive => true}, :dependent => :delete_all, :as => :target
has_many :dislikes, :conditions => {:positive => false}, :class_name => 'Like', :dependent => :delete_all, :as => :target
end
end
# @return [Integer]
def update_likes_counter
self.likes_count = self.likes.count
self.save
end
end
end

View file

@ -55,7 +55,7 @@ module Diaspora
Postzord::Dispatch.new(user, object).post
end
object.socket_to_user(user, :aspect_ids => object.parent.aspect_ids) if object.respond_to? :socket_to_user
object.socket_to_user(user) if object.respond_to? :socket_to_user
if object.after_receive(user, person)
object
end
@ -69,16 +69,18 @@ module Diaspora
#sign relayable as model creator
self.author_signature = self.sign_with_key(author.owner.encryption_key)
if !self.post_id.blank? && self.author.owns?(self.parent)
if !self.parent.blank? && self.author.owns?(self.parent)
#sign relayable as parent object owner
self.parent_author_signature = sign_with_key(author.owner.encryption_key)
end
end
# @return [Boolean]
def verify_parent_author_signature
verify_signature(self.parent_author_signature, self.parent.author)
end
# @return [Boolean]
def signature_valid?
verify_signature(self.author_signature, self.author)
end

View file

@ -11,6 +11,8 @@ module Diaspora
cattr_accessor :field_with_tags
end
model.instance_eval do
before_create :build_tags
def extract_tags_from sym
self.field_with_tags = sym
end

View file

@ -7,7 +7,7 @@ module Diaspora
module Querying
def find_visible_post_by_id( id, opts={} )
post = Post.where(:id => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).first
post = Post.where(:id => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).select("posts.*").first
post ||= Post.where(:id => id, :author_id => self.person.id).where(opts).first
post ||= Post.where(:id => id, :public => true).where(opts).first
end

View file

@ -4,14 +4,11 @@
*/
var ContentUpdater = {
elementWithGuid: function(selector, guid) {
return $(selector + "[data-guid='" + guid + "']");
},
addPostToStream: function(html) {
var streamElement = $(html);
var postId = streamElement.attr("data-guid");
var postGUID = $(streamElement).attr('id');
if($(".stream_element[data-guid='" + postId + "']").length === 0) {
if($("#"+postGUID).length === 0) {
if($("#no_posts").length) {
$("#no_posts").detach();
}
@ -20,19 +17,19 @@ var ContentUpdater = {
streamElement.find("label").inFieldLabels();
});
Diaspora.widgets.publish("stream/postAdded", [postId]);
Diaspora.widgets.publish("stream/postAdded", [postGUID]);
Diaspora.widgets.timeago.updateTimeAgo();
Diaspora.widgets.directionDetector.updateBinds();
}
},
addLikesToPost: function(postId, html) {
var post = ContentUpdater.elementWithGuid("div", postId);
addLikesToPost: function(postGUID, html) {
var post = $("#" + postGUID);
$(".likes_container", post)
.fadeOut("fast")
.html(html)
.fadeIn("fast");
}
};
};

View file

@ -79,7 +79,7 @@ var View = {
/* notification routing */
$("#notification").delegate('.hard_object_link', 'click', function(evt){
var post = $("*[data-guid='"+ $(this).attr('data-ref') +"']"),
var post = $("#"+ $(this).attr('data-ref')),
lastComment = post.find('.comment.posted').last();
if(post.length > 0){

View file

@ -33,7 +33,12 @@ var WebSocketReceiver = {
WebSocketReceiver.processPerson(obj);
} else {
WSR.debug("got a " + obj['class'] + " for aspects " + obj.aspect_ids);
debug_string = "got a " + obj['class'];
if(obj.aspect_ids !== undefined){
debug_string += " for aspects " + obj.aspect_ids;
}
WSR.debug(debug_string);
if (obj['class']=="retractions") {
WebSocketReceiver.processRetraction(obj.post_id);
@ -77,7 +82,7 @@ var WebSocketReceiver = {
},
processRetraction: function(post_id){
$("*[data-guid='" + post_id + "']").fadeOut(400, function() {
$("#" + post_id).fadeOut(400, function() {
$(this).remove();
});
if($("#main_stream")[0].childElementCount === 0) {
@ -85,11 +90,10 @@ var WebSocketReceiver = {
}
},
processComment: function(postId, commentId, html, opts) {
processComment: function(postGUID, commentGUID, html, opts) {
if( $(".comment[data-guid='"+commentId+"']").length === 0 ) {
var post = $("*[data-guid='"+postId+"']'"),
if( $("#"+commentGUID).length === 0 ) {
var post = $("#"+postGUID),
prevComments = $('.comment.posted', post);
if(prevComments.length > 0) {
@ -123,9 +127,8 @@ var WebSocketReceiver = {
Diaspora.widgets.directionDetector.updateBinds();
},
processLike: function(postId, html) {
var post = $("*[data-guid='"+postId+"']");
$('.likes', post).html(html);
processLike: function(targetGUID, html) {
$('.likes', "#" + targetGUID).first().html(html);
},
processPost: function(className, postId, html, aspectIds) {

View file

@ -302,10 +302,10 @@ ul.as-selections
:height 370px
:width 500px
time
:font
:weight normal
:size smaller
.comment
.comment_info
:font
:size smaller
.from
a
@ -667,9 +667,23 @@ form.new_comment
:display inline-block
.comments
.timeago
:color #999
.comment
.likes,
.likes_container
:display inline
:padding 0
:margin 0
:font
:size 10px
a
:font
:weight normal
img
:margin 0
:height 9px
:width 9px
:top 1px
.stream.show
ul.comments
@ -816,6 +830,7 @@ label
:cursor pointer
#publisher
:z-index 0
:color #999
:position relative
@ -2210,6 +2225,11 @@ ul.show_comments
:border
:top 1px dotted #aaa
ul.show_comments,
.likes_container
a
:color #999
.likes_container
:margin
:bottom -4px
@ -2217,18 +2237,11 @@ ul.show_comments
ul.show_comments,
.likes_container
*
:font
:weight bold
:color #999
img
:position relative
:top 3px
:top 2px
:height 12px
:width 12px
:margin
:left 0.5em
.mark_all_read
:position relative

View file

@ -15,108 +15,122 @@ describe LikesController do
sign_in :user, @user1
end
describe '#create' do
let(:like_hash) {
{:positive => 1,
:post_id => "#{@post.id}"}
}
let(:dislike_hash) {
{:positive => 0,
:post_id => "#{@post.id}"}
}
[Comment, Post].each do |class_const|
context class_const.to_s do
let(:id_field){
"#{class_const.to_s.underscore}_id"
}
context "on my own post" do
before do
@post = @user1.post :status_message, :text => "AWESOME", :to => @aspect1.id
describe '#create' do
let(:like_hash) {
{:positive => 1,
id_field => "#{@target.id}"}
}
let(:dislike_hash) {
{:positive => 0,
id_field => "#{@target.id}"}
}
context "on my own post" do
before do
@target = @user1.post :status_message, :text => "AWESOME", :to => @aspect1.id
@target = @user1.comment "hey", :post => @target if class_const == Comment
end
it 'responds to format js' do
post :create, like_hash.merge(:format => 'js')
response.code.should == '201'
end
end
context "on a post from a contact" do
before do
@target = @user2.post :status_message, :text => "AWESOME", :to => @aspect2.id
@target = @user2.comment "hey", :post => @target if class_const == Comment
end
it 'likes' do
post :create, like_hash
response.code.should == '201'
end
it 'dislikes' do
post :create, dislike_hash
response.code.should == '201'
end
it "doesn't post multiple times" do
@user1.like(1, :target => @target)
post :create, dislike_hash
response.code.should == '422'
end
end
context "on a post from a stranger" do
before do
@target = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id
@target = eve.comment "hey", :post => @target if class_const == Comment
end
it "doesn't post" do
@user1.should_not_receive(:like)
post :create, like_hash
response.code.should == '422'
end
end
end
it 'responds to format js' do
post :create, like_hash.merge(:format => 'js')
response.code.should == '201'
end
end
describe '#index' do
before do
@message = alice.post(:status_message, :text => "hey", :to => @aspect1.id)
@message = alice.comment( "hey", :post => @message) if class_const == Comment
end
it 'returns a 404 for a post not visible to the user' do
sign_in eve
get :index, id_field => @message.id
end
context "on a post from a contact" do
before do
@post = @user2.post :status_message, :text => "AWESOME", :to => @aspect2.id
it 'returns an array of likes for a post' do
like = bob.build_like(:positive => true, :target => @message)
like.save!
get :index, id_field => @message.id
assigns[:likes].map(&:id).should == @message.likes.map(&:id)
end
it 'returns an empty array for a post with no likes' do
get :index, id_field => @message.id
assigns[:likes].should == []
end
end
it 'likes' do
post :create, like_hash
response.code.should == '201'
describe '#destroy' do
before do
@message = bob.post(:status_message, :text => "hey", :to => @aspect1.id)
@message = bob.comment( "hey", :post => @message) if class_const == Comment
@like = alice.build_like(:positive => true, :target => @message)
@like.save
end
it 'lets a user destroy their like' do
expect {
delete :destroy, :format => "js", id_field => @like.target_id, :id => @like.id
}.should change(Like, :count).by(-1)
response.status.should == 200
end
it 'does not let a user destroy other likes' do
like2 = eve.build_like(:positive => true, :target => @message)
like2.save
expect {
delete :destroy, :format => "js", id_field => like2.target_id, :id => like2.id
}.should_not change(Like, :count)
response.status.should == 403
end
end
it 'dislikes' do
post :create, dislike_hash
response.code.should == '201'
end
it "doesn't post multiple times" do
@user1.like(1, :post => @post)
post :create, dislike_hash
response.code.should == '422'
end
end
context "on a post from a stranger" do
before do
@post = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id
end
it "doesn't post" do
@user1.should_not_receive(:like)
post :create, like_hash
response.code.should == '422'
end
end
end
describe '#index' do
before do
@message = alice.post(:status_message, :text => "hey", :to => @aspect1.id)
end
it 'returns a 404 for a post not visible to the user' do
sign_in eve
get :index, :post_id => @message.id
end
it 'returns an array of likes for a post' do
like = bob.build_like(:positive => true, :post => @message)
like.save!
get :index, :post_id => @message.id
assigns[:likes].map(&:id).should == @message.likes.map(&:id)
end
it 'returns an empty array for a post with no likes' do
get :index, :post_id => @message.id
assigns[:likes].should == []
end
end
describe '#destroy' do
before do
@message = bob.post(:status_message, :text => "hey", :to => @aspect1.id)
@like = alice.build_like(:positive => true, :post => @message)
@like.save
end
it 'lets a user destroy their like' do
expect {
delete :destroy, :format => "js", :post_id => @like.post_id, :id => @like.id
}.should change(Like, :count).by(-1)
response.status.should == 200
end
it 'does not let a user destroy other likes' do
like2 = eve.build_like(:positive => true, :post => @message)
like2.save
expect {
delete :destroy, :format => "js", :post_id => like2.post_id, :id => like2.id
}.should_not change(Like, :count)
response.status.should == 403
end
end
end

View file

@ -37,7 +37,7 @@ end
Factory.define :like do |x|
x.association :author, :factory => :person
x.association :post, :factory => :status_message
x.association :target, :factory => :status_message
end
Factory.define :user do |u|

View file

@ -8,10 +8,10 @@ describe NotificationsHelper do
@person = Factory(:person)
@post = Factory(:status_message, :author => @user.person)
@person2 = Factory(:person)
@notification = Notification.notify(@user, Factory(:like, :author => @person, :post => @post), @person)
@notification = Notification.notify(@user, Factory(:like, :author => @person2, :post => @post), @person2)
@notification = Notification.notify(@user, Factory(:like, :author => @person, :target => @post), @person)
@notification = Notification.notify(@user, Factory(:like, :author => @person2, :target => @post), @person2)
end
describe '#notification_people_link' do
context 'formatting' do
include ActionView::Helpers::SanitizeHelper

View file

@ -102,7 +102,7 @@ describe Notifier do
describe ".liked" do
before do
@sm = Factory(:status_message, :author => alice.person)
@like = @sm.likes.create(:author => bob.person)
@like = @sm.likes.create!(:author => bob.person)
@mail = Notifier.liked(alice.id, @like.author.id, @like.id)
end

View file

@ -21,26 +21,26 @@ describe Like do
describe 'User#like' do
it "should be able to like on one's own status" do
alice.like(1, :post => @status)
alice.like(1, :target => @status)
@status.reload.likes.first.positive.should == true
end
it "should be able to like on a contact's status" do
bob.like(0, :post => @status)
bob.like(0, :target => @status)
@status.reload.dislikes.first.positive.should == false
end
it "does not allow multiple likes" do
lambda {
alice.like(1, :post => @status)
alice.like(0, :post => @status)
alice.like(1, :target => @status)
alice.like(0, :target => @status)
}.should raise_error
end
end
describe '#notification_type' do
before do
@like = @alice.like(1, :post => @status)
@like = @alice.like(1, :target => @status)
end
it 'should be notifications liked if you are the post owner' do
@ -59,9 +59,16 @@ describe Like do
describe 'counter cache' do
it 'increments the counter cache on its post' do
lambda {
@alice.like(1, :post => @status)
@alice.like(1, :target => @status)
}.should change{ @status.reload.likes_count }.by(1)
end
it 'increments the counter cache on its comment' do
comment = Factory(:comment, :post => @status)
lambda {
@alice.like(1, :target => comment)
}.should change{ comment.reload.likes_count }.by(1)
end
end
describe 'xml' do
@ -70,7 +77,7 @@ describe Like do
@liker_aspect = @liker.aspects.create(:name => "dummies")
connect_users(alice, @alices_aspect, @liker, @liker_aspect)
@post = alice.post :status_message, :text => "huhu", :to => @alices_aspect.id
@like = @liker.like 0, :post => @post
@like = @liker.like 0, :target => @post
@xml = @like.to_xml.to_s
end
it 'serializes the sender handle' do
@ -87,7 +94,7 @@ describe Like do
@marshalled_like.author.should == @liker.person
end
it 'marshals the post' do
@marshalled_like.post.should == @post
@marshalled_like.target.should == @post
end
end
end
@ -98,11 +105,11 @@ describe Like do
@remote_parent = Factory.create(:status_message, :author => @remote_raphael)
@local_parent = @local_luke.post :status_message, :text => "foobar", :to => @local_luke.aspects.first
@object_by_parent_author = @local_luke.like(1, :post => @local_parent)
@object_by_recipient = @local_leia.build_like(:positive => 1, :post => @local_parent)
@object_by_parent_author = @local_luke.like(1, :target => @local_parent)
@object_by_recipient = @local_leia.build_like(:positive => 1, :target => @local_parent)
@dup_object_by_parent_author = @object_by_parent_author.dup
@object_on_remote_parent = @local_luke.like(0, :post => @remote_parent)
@object_on_remote_parent = @local_luke.like(0, :target => @remote_parent)
end
it_should_behave_like 'it is relayable'
end

View file

@ -96,8 +96,8 @@ describe Notification do
it 'concatinates the like notifications' do
p = Factory(:status_message, :author => @user.person)
person2 = Factory(:person)
notification = Notification.notify(@user, Factory(:like, :author => @person, :post => p), @person)
notification2 = Notification.notify(@user, Factory(:like, :author => person2, :post => p), person2)
notification = Notification.notify(@user, Factory(:like, :author => @person, :target => p), @person)
notification2 = Notification.notify(@user, Factory(:like, :author => person2, :target => p), person2)
notification.id.should == notification2.id
end
end

View file

@ -569,8 +569,8 @@ describe User do
before do
@message = alice.post(:status_message, :text => "cool", :to => alice.aspects.first)
@message2 = bob.post(:status_message, :text => "uncool", :to => bob.aspects.first)
@like = alice.like(true, :post => @message)
@like2 = bob.like(true, :post => @message)
@like = alice.like(true, :target => @message)
@like2 = bob.like(true, :target => @message)
end
describe '#like_for' do