Refactor Post Presenter

and comment presenter
This commit is contained in:
Dennis Collinson 2012-05-10 12:24:13 -07:00
parent 80511d065b
commit efa79a4ad7
27 changed files with 157 additions and 172 deletions

View file

@ -1,12 +1,4 @@
app.collections.Posts = Backbone.Collection.extend({ app.collections.Posts = Backbone.Collection.extend({
url : "/posts", model: app.models.Post,
url : "/posts"
model: function(attrs, options) {
var modelClass = app.models.Post
return new modelClass(attrs, options);
},
parse: function(resp){
return resp.posts;
}
}); });

View file

@ -1,35 +1,16 @@
(function(){ (function(){
var dateFormatter = function dateFormatter() { app.helpers.dateFormatter = {
parse:function (dateString) {
}; return new Date(dateString).getTime() || this.parseISO8601UTC(dateString || "");
dateFormatter.parse = function(date_string) {
var timestamp = new Date(date_string).getTime();
if (isNaN(timestamp)) {
timestamp = dateFormatter.parseISO8601UTC(date_string);
}
return timestamp;
}, },
dateFormatter.parseISO8601UTC = function(date_string) { parseISO8601UTC:function (dateString) {
var iso8601_utc_pattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{3}))?Z$/; var iso8601_utc_pattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(.(\d{3}))?Z$/
var time_components = date_string.match(iso8601_utc_pattern); , time_components = dateString.match(iso8601_utc_pattern)
var timestamp = 0; , timestamp = time_components && Date.UTC(time_components[1], time_components[2] - 1, time_components[3],
time_components[4], time_components[5], time_components[6], time_components[8] || 0);
if (time_components != null) { return timestamp || 0;
if (time_components[8] == undefined) {
time_components[8] = 0;
} }
timestamp = Date.UTC(time_components[1], time_components[2] - 1, time_components[3],
time_components[4], time_components[5], time_components[6],
time_components[8]);
} }
return timestamp;
},
app.helpers.dateFormatter = dateFormatter;
})(); })();

View file

@ -1,8 +1,6 @@
//= require ./content_view //= require ./content_view
app.views.Comment = app.views.Content.extend({ app.views.Comment = app.views.Content.extend({
templateName: "comment", templateName: "comment",
className : "comment media", className : "comment media",
events : function() { events : function() {

View file

@ -1,6 +1,5 @@
//= require ./stream_object_view //= require ./stream_object_view
app.views.Content = app.views.StreamObject.extend({ app.views.Content = app.views.StreamObject.extend({
events: { events: {
"click .expander": "expandPost" "click .expander": "expandPost"
}, },

View file

@ -20,7 +20,7 @@ class CommentsController < ApplicationController
if @comment if @comment
respond_to do |format| respond_to do |format|
format.json{ render :json => @comment.as_api_response(:backbone), :status => 201 } format.json{ render :json => CommentPresenter.new(@comment), :status => 201 }
format.html{ render :nothing => true, :status => 201 } format.html{ render :nothing => true, :status => 201 }
format.mobile{ render :partial => 'comment', :locals => {:post => @comment.post, :comment => @comment} } format.mobile{ render :partial => 'comment', :locals => {:post => @comment.post, :comment => @comment} }
end end
@ -56,7 +56,7 @@ class CommentsController < ApplicationController
@comments = @post.comments.for_a_stream @comments = @post.comments.for_a_stream
respond_with do |format| respond_with do |format|
format.json { render :json => @comments.as_api_response(:backbone), :status => 200 } format.json { render :json => CommentPresenter.new(@comments), :status => 200 }
format.mobile{render :layout => false} format.mobile{render :layout => false}
end end
end end

View file

@ -70,9 +70,11 @@ class LikesController < ApplicationController
def find_json_for_like def find_json_for_like
if @like.parent.is_a? Post if @like.parent.is_a? Post
PostPresenter.new(@like.parent, current_user).to_json ExtremePostPresenter.new(@like.parent, current_user).as_json
elsif @like.parent.is_a? Comment
CommentPresenter.new(@like.parent)
else else
@like.parent.as_api_response(:backbone) @like.parent.respond_to?(:as_api_response) ? @like.parent.as_api_response(:backbone) : @like.parent.as_json
end end
end end
end end

View file

@ -17,7 +17,7 @@ class ParticipationsController < ApplicationController
if @participation if @participation
respond_to do |format| respond_to do |format|
format.mobile { redirect_to post_path(@participation.post_id) } format.mobile { redirect_to post_path(@participation.post_id) }
format.json { render :json => PostPresenter.new(@participation.parent, current_user).to_json, :status => 201 } format.json { render :json => ExtremePostPresenter.new(@participation.parent, current_user), :status => 201 }
end end
else else
render :nothing => true, :status => 422 render :nothing => true, :status => 422
@ -30,7 +30,7 @@ class ParticipationsController < ApplicationController
if @participation if @participation
current_user.retract(@participation) current_user.retract(@participation)
respond_to do |format| respond_to do |format|
format.json { render :json => PostPresenter.new(@participation.parent, current_user).to_json, :status => 202 } format.json { render :json => ExtremePostPresenter.new(@participation.parent, current_user), :status => 202 }
end end
else else
respond_to do |format| respond_to do |format|

View file

@ -91,8 +91,7 @@ class PeopleController < ApplicationController
@aspect = :profile @aspect = :profile
@share_with = (params[:share_with] == 'true') @share_with = (params[:share_with] == 'true')
@stream = Stream::Person.new(current_user, @person, @stream = Stream::Person.new(current_user, @person, :max_time => max_time)
:max_time => max_time)
@profile = @person.profile @profile = @person.profile
@ -120,14 +119,15 @@ class PeopleController < ApplicationController
if params[:ex] if params[:ex]
@page = :experimental @page = :experimental
gon.person = PersonPresenter.new(@person, current_user) gon.person = PersonPresenter.new(@person, current_user)
gon.stream = @stream.stream_posts.as_api_response(:backbone) gon.stream = PostPresenter.collection_json(@stream.stream_posts, current_user)
render :nothing => true, :layout => 'post' render :nothing => true, :layout => 'post'
else else
respond_with @person, :locals => {:post_type => :all} respond_with @person, :locals => {:post_type => :all}
end end
end end
format.json{ render_for_api :backbone, :json => @stream.stream_posts, :root => :posts }
format.json { render :json => PostPresenter.collection_json(@stream.stream_posts, current_user) }
end end
end end

View file

@ -30,10 +30,10 @@ class PostsController < ApplicationController
mark_corresponding_notification_read if user_signed_in? mark_corresponding_notification_read if user_signed_in?
respond_to do |format| respond_to do |format|
format.html{ gon.post = post_json(@post); render 'posts/show.html.haml' } format.html{ gon.post = ExtremePostPresenter.new(@post, current_user); render 'posts/show.html.haml' }
format.xml{ render :xml => @post.to_diaspora_xml } format.xml{ render :xml => @post.to_diaspora_xml }
format.mobile{render 'posts/show.mobile.haml', :layout => "application"} format.mobile{render 'posts/show.mobile.haml', :layout => "application"}
format.json{ render :json => post_json(@post) } format.json{ render :json => ExtremePostPresenter.new(@post, current_user) }
end end
end end
@ -81,7 +81,7 @@ class PostsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html{ redirect_to post_path(next_post) } format.html{ redirect_to post_path(next_post) }
format.json{ render :json => post_json(next_post) } format.json{ render :json => ExtremePostPresenter.new(next_post, current_user)}
end end
end end
@ -90,7 +90,7 @@ class PostsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html{ redirect_to post_path(previous_post) } format.html{ redirect_to post_path(previous_post) }
format.json{ render :json => post_json(previous_post) } format.json{ render :json => ExtremePostPresenter.new(previous_post, current_user)}
end end
end end
@ -111,10 +111,6 @@ class PostsController < ApplicationController
Post.visible_from_author(@post.author, current_user) Post.visible_from_author(@post.author, current_user)
end end
def post_json(post)
PostPresenter.new(post, current_user).to_json
end
def find_by_guid_or_id_with_current_user(id) def find_by_guid_or_id_with_current_user(id)
key = id.to_s.length <= 8 ? :id : :guid key = id.to_s.length <= 8 ? :id : :guid
if user_signed_in? if user_signed_in?

View file

@ -9,6 +9,6 @@ class ResharesController < ApplicationController
current_user.dispatch_post(@reshare, :url => post_url(@reshare), :additional_subscribers => @reshare.root.author) current_user.dispatch_post(@reshare, :url => post_url(@reshare), :additional_subscribers => @reshare.root.author)
end end
render :json => PostPresenter.new(@reshare, current_user).to_json, :status => 201 render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201
end end
end end

View file

@ -72,7 +72,7 @@ class StatusMessagesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.mobile { redirect_to stream_path } format.mobile { redirect_to stream_path }
format.json { render :json => @status_message.as_api_response(:backbone), :status => 201 } format.json { render :json => PostPresenter.new(@status_message, current_user), :status => 201 }
end end
else else
respond_to do |format| respond_to do |format|

View file

@ -65,7 +65,7 @@ class StreamsController < ApplicationController
respond_with do |format| respond_with do |format|
format.html { render 'layouts/main_stream' } format.html { render 'layouts/main_stream' }
format.mobile { render 'layouts/main_stream' } format.mobile { render 'layouts/main_stream' }
format.json {render_for_api :backbone, :json => @stream.stream_posts, :root => :posts } format.json { render :json => PostPresenter.collection_json(@stream.stream_posts, current_user) }
end end
end end

View file

@ -35,7 +35,7 @@ class TagsController < ApplicationController
@stream = Stream::Tag.new(current_user, params[:name], :max_time => max_time, :page => params[:page]) @stream = Stream::Tag.new(current_user, params[:name], :max_time => max_time, :page => params[:page])
respond_with do |format| respond_with do |format|
format.json{ render_for_api :backbone, :json => @stream.stream_posts, :root => :posts } format.json{ render :json => PostPresenter.collection_json(@stream.stream_posts, current_user) }
end end
end end

View file

@ -16,16 +16,6 @@ class Comment < ActiveRecord::Base
extract_tags_from :text extract_tags_from :text
before_create :build_tags before_create :build_tags
# NOTE API V1 to be extracted
acts_as_api
api_accessible :backbone do |t|
t.add :id
t.add :guid
t.add :text
t.add :author
t.add :created_at
end
xml_attr :text xml_attr :text
xml_attr :diaspora_handle xml_attr :diaspora_handle

View file

@ -17,37 +17,6 @@ class Post < ActiveRecord::Base
attr_accessor :user_like, attr_accessor :user_like,
:user_participation :user_participation
# NOTE API V1 to be extracted
acts_as_api
api_accessible :backbone do |t|
t.add :id
t.add :guid
t.add lambda { |post|
post.raw_message
}, :as => :text
t.add :public
t.add :created_at
t.add :interacted_at
t.add :comments_count
t.add :likes_count
t.add :reshares_count
t.add :last_three_comments
t.add :provider_display_name
t.add :author
t.add :post_type
t.add :image_url
t.add :object_url
t.add :root
t.add :o_embed_cache
t.add :user_like
t.add :user_participation
t.add :mentioned_people
t.add :photos
t.add :nsfw
t.add :favorite
t.add :frame_name
end
xml_attr :provider_display_name xml_attr :provider_display_name
has_many :mentions, :dependent => :destroy has_many :mentions, :dependent => :destroy
@ -129,6 +98,21 @@ class Post < ActiveRecord::Base
scope scope
end end
def reshare_for(user)
return unless user
reshares.where(:author_id => user.person.id).first
end
def participation_for(user)
return unless user
participations.where(:author_id => user.person.id).first
end
def like_for(user)
return unless user
likes.where(:author_id => user.person.id).first
end
############# #############
def self.diaspora_initialize(params) def self.diaspora_initialize(params)

View file

@ -42,7 +42,7 @@ class Reshare < Post
end end
def photos def photos
self.root ? root.photos : nil self.root ? root.photos : []
end end
def frame_name def frame_name
@ -66,7 +66,7 @@ class Reshare < Post
end end
def nsfw def nsfw
root.nsfw root.try(:nsfw)
end end
private private

View file

@ -0,0 +1,15 @@
class CommentPresenter < BasePresenter
def initialize(comment)
@comment = comment
end
def as_json(opts={})
{
:id => @comment.id,
:guid => @comment.guid,
:text => @comment.text,
:author => @comment.author.as_api_response(:backbone),
:created_at => @comment.created_at
}
end
end

View file

@ -0,0 +1,14 @@
#this file should go away, hence the name that is so full of lulz
#post interactions should probably be a decorator, and used in very few places... maybe?
class ExtremePostPresenter
def initialize(post, current_user)
@post = post
@current_user = current_user
end
def as_json(options={})
post = PostPresenter.new(@post, @current_user)
interactions = PostInteractionPresenter.new(@post, @current_user)
post.as_json.merge!(interactions.as_json)
end
end

View file

@ -8,24 +8,41 @@ class PostPresenter
@current_user = current_user @current_user = current_user
end end
def to_json(options = {}) def self.collection_json(collection, current_user)
@post.as_api_response(:backbone).update( collection.map {|post| PostPresenter.new(post, current_user)}
end
def as_json(options={})
{ {
:user_like => user_like, :id => @post.id,
:user_participation => user_participation, :guid => @post.guid,
:likes_count => @post.likes.count, :text => @post.raw_message,
:participations_count => @post.participations.count, :public => @post.public,
:reshares_count => @post.reshares.count, :created_at => @post.created_at,
:user_reshare => user_reshare, :interacted_at => @post.interacted_at,
:comments_count => @post.comments_count,
:likes_count => @post.likes_count,
:reshares_count => @post.reshares_count,
:provider_display_name => @post.provider_display_name,
:post_type => @post.post_type,
:image_url => @post.image_url,
:object_url => @post.object_url,
:nsfw => @post.nsfw,
:favorite => @post.favorite,
:last_three_comments => CommentPresenter.as_collection(@post.last_three_comments),
:author => @post.author.as_api_response(:backbone),
:o_embed_cache => @post.o_embed_cache.try(:as_api_response, :backbone),
:mentioned_people => @post.mentioned_people.as_api_response(:backbone),
:photos => @post.photos.map {|p| p.as_api_response(:backbone)},
:frame_name => @post.frame_name || template_name,
:root => root,
:title => title,
:next_post => next_post_path, :next_post => next_post_path,
:previous_post => previous_post_path, :previous_post => previous_post_path,
:likes => likes, :user_like => user_like,
:reshares => reshares, :user_participation => user_participation,
:comments => comments, :user_reshare => user_reshare,
:participations => participations, }
:frame_name => @post.frame_name || template_name,
:title => title
})
end end
def next_post_path def next_post_path
@ -36,56 +53,31 @@ class PostPresenter
Rails.application.routes.url_helpers.previous_post_path(@post) Rails.application.routes.url_helpers.previous_post_path(@post)
end end
def comments
as_api(@post.comments)
end
def likes
as_api(@post.likes)
end
def reshares
as_api(@post.reshares)
end
def participations
as_api(@post.participations)
end
def user_like def user_like
return unless user_signed_in? @post.like_for(@current_user).try(:as_api_response, :backbone)
@post.likes.where(:author_id => person.id).first.try(:as_api_response, :backbone)
end end
def user_participation def user_participation
return unless user_signed_in? @post.participation_for(@current_user).try(:as_api_response, :backbone)
@post.participations.where(:author_id => person.id).first.try(:as_api_response, :backbone)
end end
def user_reshare def user_reshare
return unless user_signed_in? @post.reshare_for(@current_user)
@post.reshares.where(:author_id => person.id).first
end end
def title def title
if @post.text.present? @post.text.present? ? @post.text(:plain_text => true) : I18n.translate('posts.presenter.title', :name => @post.author.name)
@post.text(:plain_text => true)
else
I18n.translate('posts.presenter.title', :name => @post.author.name)
end
end end
def template_name #kill me, lol, I should be client side def template_name #kill me, lol, I should be client side
@template_name ||= TemplatePicker.new(@post).template_name @template_name ||= TemplatePicker.new(@post).template_name
end end
protected def root
PostPresenter.new(@post.root, current_user).as_json if @post.respond_to?(:root)
end
def as_api(collection) protected
collection.includes(:author => :profile).all.map do |element|
element.as_api_response(:backbone)
end
end
def person def person
@current_user.person @current_user.person
@ -95,3 +87,25 @@ class PostPresenter
@current_user.present? @current_user.present?
end end
end end
class PostInteractionPresenter
def initialize(post, current_user)
@post = post
@current_user = current_user
end
def as_json(options={})
{
:likes => as_api(@post.likes),
:reshares => as_api(@post.reshares),
:comments => CommentPresenter.as_collection(@post.comments),
:participations => as_api(@post.participations)
}
end
def as_api(collection)
collection.includes(:author => :profile).all.map do |element|
element.as_api_response(:backbone)
end
end
end

View file

@ -12,7 +12,7 @@ module Diaspora
# @return [Array<Comment>] # @return [Array<Comment>]
def last_three_comments def last_three_comments
return if self.comments_count == 0 return [] if self.comments_count == 0
# DO NOT USE .last(3) HERE. IT WILL FETCH ALL COMMENTS AND RETURN THE LAST THREE # DO NOT USE .last(3) HERE. IT WILL FETCH ALL COMMENTS AND RETURN THE LAST THREE
# INSTEAD OF DOING THE FOLLOWING, AS EXPECTED (THX AR): # INSTEAD OF DOING THE FOLLOWING, AS EXPECTED (THX AR):
self.comments.order('created_at DESC').limit(3).includes(:author => :profile).reverse self.comments.order('created_at DESC').limit(3).includes(:author => :profile).reverse

View file

@ -178,10 +178,10 @@ describe PostsController do
let(:next_post){ mock_model(StatusMessage, :id => 34)} let(:next_post){ mock_model(StatusMessage, :id => 34)}
context "GET .json" do context "GET .json" do
let(:mock_presenter) { mock(:to_json => {:title => "the unbearable lightness of being"}) } let(:mock_presenter) { mock(:as_json => {:title => "the unbearable lightness of being"}) }
it "should return a show presenter the next post" do it "should return a show presenter the next post" do
PostPresenter.should_receive(:new).with(next_post, alice).and_return(mock_presenter) ExtremePostPresenter.should_receive(:new).with(next_post, alice).and_return(mock_presenter)
get :next, :id => 14, :format => :json get :next, :id => 14, :format => :json
response.body.should == {:title => "the unbearable lightness of being"}.to_json response.body.should == {:title => "the unbearable lightness of being"}.to_json
end end
@ -205,10 +205,10 @@ describe PostsController do
let(:previous_post){ mock_model(StatusMessage, :id => 11)} let(:previous_post){ mock_model(StatusMessage, :id => 11)}
context "GET .json" do context "GET .json" do
let(:mock_presenter) { mock(:to_json => {:title => "existential crises"})} let(:mock_presenter) { mock(:as_json => {:title => "existential crises"})}
it "should return a show presenter the next post" do it "should return a show presenter the next post" do
PostPresenter.should_receive(:new).with(previous_post, alice).and_return(mock_presenter) ExtremePostPresenter.should_receive(:new).with(previous_post, alice).and_return(mock_presenter)
get :previous, :id => 14, :format => :json get :previous, :id => 14, :format => :json
response.body.should == {:title => "existential crises"}.to_json response.body.should == {:title => "existential crises"}.to_json
end end

View file

@ -32,7 +32,7 @@ describe("app.models.Stream", function() {
var fetchedSpy = jasmine.createSpy() var fetchedSpy = jasmine.createSpy()
this.stream.bind('fetched', fetchedSpy) this.stream.bind('fetched', fetchedSpy)
this.stream.fetch() this.stream.fetch()
postFetch.resolve({posts : [1,2,3]}) postFetch.resolve([1,2,3])
expect(fetchedSpy).toHaveBeenCalled() expect(fetchedSpy).toHaveBeenCalled()
}) })
@ -40,7 +40,7 @@ describe("app.models.Stream", function() {
var fetchedSpy = jasmine.createSpy() var fetchedSpy = jasmine.createSpy()
this.stream.bind('allItemsLoaded', fetchedSpy) this.stream.bind('allItemsLoaded', fetchedSpy)
this.stream.fetch() this.stream.fetch()
postFetch.resolve({posts : []}) postFetch.resolve([])
expect(fetchedSpy).toHaveBeenCalled() expect(fetchedSpy).toHaveBeenCalled()
}) })
@ -48,7 +48,7 @@ describe("app.models.Stream", function() {
var fetchedSpy = jasmine.createSpy() var fetchedSpy = jasmine.createSpy()
this.stream.bind('allItemsLoaded', fetchedSpy) this.stream.bind('allItemsLoaded', fetchedSpy)
this.stream.fetch() this.stream.fetch()
postFetch.resolve({posts : factory.post().attributes}) postFetch.resolve(factory.post().attributes)
expect(fetchedSpy).toHaveBeenCalled() expect(fetchedSpy).toHaveBeenCalled()
}) })
}); });

View file

@ -9,7 +9,7 @@ describe("app.views.Feedback", function(){
'limited' : "Limted" 'limited' : "Limted"
}}) }})
var posts = $.parseJSON(spec.readFixture("stream_json"))["posts"]; var posts = $.parseJSON(spec.readFixture("stream_json"));
this.post = new app.models.Post(posts[0]); this.post = new app.models.Post(posts[0]);
this.view = new app.views.Feedback({model: this.post}); this.view = new app.views.Feedback({model: this.post});

View file

@ -9,7 +9,7 @@ describe("app.views.LikesInfo", function(){
} }
}) })
var posts = $.parseJSON(spec.readFixture("stream_json"))["posts"]; var posts = $.parseJSON(spec.readFixture("stream_json"));
this.post = new app.models.Post(posts[0]); // post with a like this.post = new app.models.Post(posts[0]); // post with a like
this.view = new app.views.LikesInfo({model: this.post}); this.view = new app.views.LikesInfo({model: this.post});
}); });

View file

@ -19,7 +19,7 @@ describe("app.views.StreamPost", function(){
} }
}}) }})
var posts = $.parseJSON(spec.readFixture("stream_json"))["posts"]; var posts = $.parseJSON(spec.readFixture("stream_json"));
this.collection = new app.collections.Posts(posts); this.collection = new app.collections.Posts(posts);
this.statusMessage = this.collection.models[0]; this.statusMessage = this.collection.models[0];

View file

@ -2,7 +2,7 @@ describe("app.views.Stream", function() {
beforeEach(function() { beforeEach(function() {
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
this.posts = $.parseJSON(spec.readFixture("stream_json"))["posts"]; this.posts = $.parseJSON(spec.readFixture("stream_json"));
this.stream = new app.models.Stream(); this.stream = new app.models.Stream();
this.stream.add(this.posts); this.stream.add(this.posts);

View file

@ -11,13 +11,13 @@ describe PostPresenter do
@presenter.should_not be_nil @presenter.should_not be_nil
end end
describe '#to_json' do describe '#as_json' do
it 'works with a user' do it 'works with a user' do
@presenter.to_json.should be_a Hash @presenter.as_json.should be_a Hash
end end
it 'works without a user' do it 'works without a user' do
@unauthenticated_presenter.to_json.should be_a Hash @unauthenticated_presenter.as_json.should be_a Hash
end end
end end