Merge branch 'stream-object'

This commit is contained in:
danielgrippi 2011-09-11 14:18:19 -07:00
commit 586746868c
15 changed files with 385 additions and 232 deletions

View file

@ -2,6 +2,8 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, "lib", "aspect_stream")
class AspectsController < ApplicationController
before_filter :authenticate_user!
before_filter :save_sort_order, :only => :index
@ -11,38 +13,19 @@ class AspectsController < ApplicationController
respond_to :json, :only => [:show, :create]
helper_method :tags, :tag_followings
helper_method :all_aspects_selected?
helper_method :selected_people
def index
@aspects = current_user.aspects
@aspects = @aspects.where(:id => params[:a_ids]) if params[:a_ids]
@aspect_ids = @aspects.map { |a| a.id }
@posts = current_user.visible_posts(:by_members_of => @aspect_ids,
:type => ['StatusMessage','Reshare', 'ActivityStreams::Photo'],
:order => session[:sort_order] + ' DESC',
:max_time => params[:max_time].to_i
).includes(:mentions => {:person => :profile}, :author => :profile)
aspect_ids = (params[:a_ids] ? params[:a_ids] : [])
@stream = AspectStream.new(current_user, aspect_ids,
:order => session[:sort_order],
:max_time => params[:max_time].to_i)
if params[:only_posts]
render :partial => 'shared/stream', :locals => {:posts => @posts}
else
@contact_count = selected_people(@aspect_ids).count
# aspects.first is used for mobile
# the :all is currently used for view switching logic
@aspect = (params[:a_ids] ? @aspects.first : :all)
render :partial => 'shared/stream', :locals => {:posts => @stream.posts}
end
end
def selected_people(aspect_ids)
@selected_people ||= Person.joins(:contacts => :aspect_memberships).
where(:contacts => {:user_id => current_user.id},
:aspect_memberships => {:aspect_id => @aspect_ids}).
select("DISTINCT people.*").includes(:profile)
end
def create
@aspect = current_user.aspects.create(params[:aspect])
@ -100,8 +83,7 @@ class AspectsController < ApplicationController
end
def show
@aspect = current_user.aspects.where(:id => params[:id]).first
if @aspect
if @aspect = current_user.aspects.where(:id => params[:id]).first
redirect_to aspects_path('a_ids[]' => @aspect.id)
else
redirect_to aspects_path
@ -156,10 +138,6 @@ class AspectsController < ApplicationController
params[:max_time] ||= Time.now + 1
end
def all_aspects_selected?
@aspect == :all
end
def tag_followings
if current_user
if @tag_followings == nil

View file

@ -11,7 +11,7 @@ module StreamHelper
elsif controller.instance_of?(PeopleController)
person_path(@person, :max_time => @posts.last.created_at.to_i)
elsif controller.instance_of?(AspectsController)
aspects_path(:max_time => @posts.last.send(session[:sort_order].to_sym).to_i, :a_ids => params[:a_ids])
aspects_path(:max_time => @stream.posts.last.send(@stream.order.to_sym).to_i, :a_ids => @stream.aspect_ids)
else
raise 'in order to use pagination for this new controller, update next_page_path in stream helper'
end

View file

@ -48,6 +48,14 @@ class Person < ActiveRecord::Base
scope :local, where('people.owner_id IS NOT NULL')
scope :for_json, select('DISTINCT people.id, people.diaspora_handle').includes(:profile)
# @note user is passed in here defensively
scope :all_from_aspects, lambda { |aspect_ids, user|
joins(:contacts => :aspect_memberships).
where(:contacts => {:user_id => user.id},
:aspect_memberships => {:aspect_id => aspect_ids}).
select("DISTINCT people.*").includes(:profile)
}
def self.featured_users
AppConfig[:featured_users].present? ? Person.where(:diaspora_handle => AppConfig[:featured_users]) : []
end
@ -68,13 +76,13 @@ class Person < ActiveRecord::Base
def self.find_from_id_or_username(params)
p = if params[:id].present?
Person.where(:id => params[:id]).first
elsif params[:username].present? && u = User.find_by_username(params[:username])
u.person
else
nil
end
p = if params[:id].present?
Person.where(:id => params[:id]).first
elsif params[:username].present? && u = User.find_by_username(params[:username])
u.person
else
nil
end
raise ActiveRecord::RecordNotFound unless p.present?
p
end
@ -126,6 +134,8 @@ class Person < ActiveRecord::Base
Person.searchable.where(sql, *tokens)
end
def name(opts = {})
if self.profile.nil?
fix_profile

View file

@ -6,24 +6,24 @@
#sort_by
= t('.recently')
%span.controls
= link_to_if(session[:sort_order] == 'created_at', t('.commented_on'), aspects_path(:a_ids => params[:a_ids], :sort_order => 'updated_at'))
= link_to_if(session[:sort_order] == 'created_at', t('.commented_on'), aspects_path(:a_ids => stream.aspect_ids, :sort_order => 'updated_at'))
·
= link_to_if(session[:sort_order] == 'updated_at', t('.posted'), aspects_path(:a_ids => params[:a_ids], :sort_order => 'created_at' ))
= link_to_if(session[:sort_order] == 'updated_at', t('.posted'), aspects_path(:a_ids => stream.aspect_ids, :sort_order => 'created_at' ))
%h3
- if all_aspects_selected?
- if stream.for_all_aspects?
= t('.stream')
- else
= @aspects.to_sentence
= stream.aspects.to_sentence
= render 'shared/publisher', :selected_aspects => @aspects, :aspect_ids => aspect_ids, :aspect => @aspect
= render 'shared/publisher', :selected_aspects => stream.aspects, :aspect_ids => stream.aspect_ids, :for_all_aspects => stream.for_all_aspects?, :aspect => stream.aspect
= render 'aspects/no_posts_message'
- if current_user.contacts.size < 2
= render 'aspects/no_contacts_message'
#main_stream.stream{:data => {:guids => aspect_ids.join(',')}}
- if posts.length > 0
= render 'shared/stream', :posts => posts
#main_stream.stream{:data => {:guids => stream.aspect_ids.join(',')}}
- if stream.posts.length > 0
= render 'shared/stream', :posts => stream.posts
#pagination
=link_to(t('more'), next_page_path, :class => 'paginate')

View file

@ -1,22 +1,22 @@
#selected_aspect_contacts.section
.title.no_icon
%h5
- if all_aspects_selected? || !@aspect || @aspect_ids.size > 1
- if @stream.for_all_aspects? || @stream.aspect_ids.size > 1
= "#{t('_contacts')}"
- else
= @aspect.name
= "(#{count})"
= @stream.aspect.name
= "(#{@stream.people.size})"
.content
- if people.size > 0
- for person in people
= person_image_link person
- if @stream.people.size > 0
- for person in @stream.people
= person_image_link(person)
- if all_aspects_selected? || @aspect_ids.size > 1
- if @stream.for_all_aspects? || @stream.aspect_ids.size > 1
= link_to t('.view_all_contacts'), contacts_link, :id => "view_all_contacts_link"
- else
= link_to t('.view_all_contacts'), contacts_path(:a_id => @aspect.id), :id => "view_all_contacts_link"
= link_to t('.view_all_contacts'), contacts_path(:a_id => @stream.aspect.id), :id => "view_all_contacts_link"
- else
= t('.no_contacts')

View file

@ -26,12 +26,9 @@
.span-13.append-1
#aspect_stream_container.stream_container
= render 'aspect_stream',
:aspect => @aspect,
:aspect_ids => @aspect_ids,
:posts => @posts
= render 'aspect_stream', :stream => @stream
.span-5.rightBar.last
= render 'selected_contacts', :people => selected_people(@aspect_ids).sample(20), :count => @contact_count
= render 'selected_contacts', :stream => @stream
= render 'shared/right_sections'

View file

@ -1,3 +1,3 @@
$('#aspect_stream_container').html("<%= escape_javascript(render('aspects/aspect_stream', :aspect => @aspect, :aspect_ids => @aspect_ids, :posts => @posts)) %>");
$('#selected_aspect_contacts').html("<%= escape_javascript(render('aspects/selected_contacts', :people => @selected_people.sample(20), :count => @contact_count )) %>");
$('#aspect_stream_container').html("<%= escape_javascript(render('aspects/aspect_stream', :stream => @stream)) %>");
$('#selected_aspect_contacts').html("<%= escape_javascript(render('aspects/selected_contacts', :people => @stream.people.sample(20), :count => @stream.people.size)) %>");
$('#aspect_stream_container a[rel*=facebox]').facebox();

View file

@ -3,18 +3,18 @@
-# the COPYRIGHT file.
#aspect_header
- if @aspect == :all
- if @stream.for_all_aspects?
= t('all_aspects')
- else
= @aspect
= @stream.aspect
= link_to t('.post_a_message'), '#publisher_page', :id => 'publisher_button'
#main_stream.stream
= render 'shared/stream', :posts => @posts
-if @posts.length > 0
= render 'shared/stream', :posts => @stream.posts
-if @stream.posts.length > 0
#pagination
%a.more-link.paginate{:href => next_page_path}
%h2= t("more")
- content_for :subpages do
= render 'shared/publisher', :aspect_ids => @aspect_ids, :selected_aspects => @aspects, :aspect => @aspect
= render 'shared/publisher', :aspect_ids => @stream.aspect_ids, :selected_aspects => @stream.aspects, :aspect => @stream.aspect

View file

@ -32,7 +32,7 @@
.public_toggle
%span#publisher_service_icons
= t("shared.publisher.click_to_share_with")
- if aspect == :all || aspect == :profile
- if((defined?(for_all_aspects) && for_all_aspects) || aspect == :profile)
= status.hidden_field(:public)
= image_tag "icons/globe.png", :title => t('javascripts.publisher.limited'), :class => 'public_icon dim', :width => 16, :height => 16
- if current_user.services

View file

@ -25,12 +25,12 @@
= status.text_area :text
- for aspect_id in @aspect_ids
- for aspect_id in aspect_ids
= hidden_field_tag 'aspect_ids[]', aspect_id.to_s
%fieldset
- unless params[:a_ids]
- unless aspect_ids
%input{:type => 'checkbox', :name => 'status_message[public]', :id => 'public', :class => 'custom', :value => 'true'}
%label{:for => 'public'}
= t('.make_public')

72
lib/aspect_stream.rb Normal file
View file

@ -0,0 +1,72 @@
# Copyright (c) 2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class AspectStream
attr_reader :max_time, :order
# @param user [User]
# @param inputted_aspect_ids [Array<Integer>] Ids of aspects for given stream
# @param aspect_ids [Array<Integer>] Aspects this stream is responsible for
# @opt max_time [Integer] Unix timestamp of stream's post ceiling
# @opt order [String] Order of posts (i.e. 'created_at', 'updated_at')
# @return [void]
def initialize(user, inputted_aspect_ids, opts={})
@user = user
@inputted_aspect_ids = inputted_aspect_ids
@max_time = opts[:max_time]
@order = opts[:order]
end
# Filters aspects given the stream's aspect ids on initialization and the user.
# Will disclude aspects from inputted aspect ids if user is not associated with their
# target aspects.
#
# @return [ActiveRecord::Association<Aspect>] Filtered aspects given the stream's user
def aspects
@aspects ||= lambda do
a = @user.aspects
a = a.where(:id => @inputted_aspect_ids) if @inputted_aspect_ids.length > 0
a
end.call
end
# Maps ids into an array from #aspects
#
# @return [Array<Integer>] Aspect ids
def aspect_ids
@aspect_ids ||= aspects.map { |a| a.id }
end
# @return [ActiveRecord::Association<Post>] AR association of posts
def posts
# NOTE(this should be something like Post.all_for_stream(@user, aspect_ids, {}) that calls visible_posts
@posts ||= @user.visible_posts(:by_members_of => @aspect_ids,
:type => ['StatusMessage', 'Reshare', 'ActivityStreams::Photo'],
:order => "#{@order} DESC",
:max_time => @max_time
).includes(:mentions => {:person => :profile}, :author => :profile)
end
# @return [ActiveRecord::Association<Person>] AR association of people within stream's given aspects
def people
@people ||= Person.all_from_aspects(aspect_ids, @user)
end
# @note aspects.first is used for mobile. NOTE(this is a hack and should be fixed)
# @return [Aspect,Symbol]
def aspect
if !for_all_aspects? || aspects.size == 1
aspects.first
end
end
# Determine whether or not the stream is displaying across
# all of the user's aspects.
#
# @return [Boolean]
def for_all_aspects?
@all_aspects ||= aspect_ids.length == @user.aspects.size
end
end

View file

@ -66,61 +66,92 @@ describe AspectsController do
end
describe "#index" do
it "generates a jasmine fixture", :fixture => true do
context 'jasmine fixtures' do
it "generates a jasmine fixture", :fixture => true do
get :index
save_fixture(html_for("body"), "aspects_index")
end
it "generates a jasmine fixture with a prefill", :fixture => true do
get :index, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_prefill")
end
it 'generates a jasmine fixture with services', :fixture => true do
alice.services << Services::Facebook.create(:user_id => alice.id)
alice.services << Services::Twitter.create(:user_id => alice.id)
get :index, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_services")
end
it 'generates a jasmine fixture with posts', :fixture => true do
bob.post(:status_message, :text => "Is anyone out there?", :to => @bob.aspects.where(:name => "generic").first.id)
message = alice.post(:status_message, :text => "hello "*800, :to => @alices_aspect_2.id)
5.times { bob.comment("what", :post => message) }
get :index
save_fixture(html_for("body"), "aspects_index_with_posts")
end
it "generates a jasmine fixture with a post with comments", :fixture => true do
message = bob.post(:status_message, :text => "HALO WHIRLED", :to => @bob.aspects.where(:name => "generic").first.id)
5.times { bob.comment("what", :post => message) }
get :index
save_fixture(html_for("body"), "aspects_index_post_with_comments")
end
it 'generates a jasmine fixture with a followed tag', :fixture => true do
@tag = ActsAsTaggableOn::Tag.create!(:name => "partytimeexcellent")
TagFollowing.create!(:tag => @tag, :user => alice)
get :index
save_fixture(html_for("body"), "aspects_index_with_one_followed_tag")
end
it "generates a jasmine fixture with a post containing a video", :fixture => true do
stub_request(:get, "http://gdata.youtube.com/feeds/api/videos/UYrkQL1bX4A?v=2").
with(:headers => {'Accept'=>'*/*'}).
to_return(:status => 200, :body => "<title>LazyTown song - Cooking By The Book</title>", :headers => {})
alice.post(:status_message, :text => "http://www.youtube.com/watch?v=UYrkQL1bX4A", :to => @alices_aspect_2.id)
get :index
save_fixture(html_for("body"), "aspects_index_with_video_post")
end
it "generates a jasmine fixture with a post that has been liked", :fixture => true do
message = alice.post(:status_message, :text => "hello "*800, :to => @alices_aspect_2.id)
alice.build_like(:positive => true, :target => message).save
bob.build_like(:positive => true, :target => message).save
get :index
save_fixture(html_for("body"), "aspects_index_with_a_post_with_likes")
end
end
it 'renders just the stream with the infinite scroll param set' do
get :index, :only_posts => true
response.should render_template('shared/_stream')
end
it 'assigns an AspectStream' do
get :index
save_fixture(html_for("body"), "aspects_index")
assigns(:stream).class.should == AspectStream
end
it "generates a jasmine fixture with a prefill", :fixture => true do
get :index, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_prefill")
end
describe 'filtering by aspect' do
before do
@aspect1 = alice.aspects.create(:name => "test aspect")
@stream = AspectStream.new(alice, [])
@stream.stub(:posts).and_return([])
end
it 'generates a jasmine fixture with services', :fixture => true do
alice.services << Services::Facebook.create(:user_id => alice.id)
alice.services << Services::Twitter.create(:user_id => alice.id)
get :index, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_services")
end
it 'respects a single aspect' do
AspectStream.should_receive(:new).with(alice, [@aspect1.id], anything).and_return(@stream)
get :index, :a_ids => [@aspect1.id]
end
it 'generates a jasmine fixture with posts', :fixture => true do
bob.post(:status_message, :text => "Is anyone out there?", :to => @bob.aspects.where(:name => "generic").first.id)
message = alice.post(:status_message, :text => "hello "*800, :to => @alices_aspect_2.id)
5.times { bob.comment("what", :post => message) }
get :index
save_fixture(html_for("body"), "aspects_index_with_posts")
end
it "generates a jasmine fixture with a post with comments", :fixture => true do
message = bob.post(:status_message, :text => "HALO WHIRLED", :to => @bob.aspects.where(:name => "generic").first.id)
5.times { bob.comment("what", :post => message) }
get :index
save_fixture(html_for("body"), "aspects_index_post_with_comments")
end
it 'generates a jasmine fixture with a followed tag', :fixture => true do
@tag = ActsAsTaggableOn::Tag.create!(:name => "partytimeexcellent")
TagFollowing.create!(:tag => @tag, :user => alice)
get :index
save_fixture(html_for("body"), "aspects_index_with_one_followed_tag")
end
it "generates a jasmine fixture with a post containing a video", :fixture => true do
stub_request(:get, "http://gdata.youtube.com/feeds/api/videos/UYrkQL1bX4A?v=2").
with(:headers => {'Accept'=>'*/*'}).
to_return(:status => 200, :body => "<title>LazyTown song - Cooking By The Book</title>", :headers => {})
alice.post(:status_message, :text => "http://www.youtube.com/watch?v=UYrkQL1bX4A", :to => @alices_aspect_2.id)
get :index
save_fixture(html_for("body"), "aspects_index_with_video_post")
end
it "generates a jasmine fixture with a post that has been liked", :fixture => true do
message = alice.post(:status_message, :text => "hello "*800, :to => @alices_aspect_2.id)
alice.build_like(:positive => true, :target => message).save
bob.build_like(:positive => true, :target => message).save
get :index
save_fixture(html_for("body"), "aspects_index_with_a_post_with_likes")
it 'respects multiple aspects' do
aspect2 = alice.aspects.create(:name => "test aspect two")
AspectStream.should_receive(:new).with(alice, [@aspect1.id, aspect2.id], anything).and_return(@stream)
get :index, :a_ids => [@aspect1.id, aspect2.id]
end
end
context "mobile" do
@ -135,117 +166,6 @@ describe AspectsController do
end
end
context 'with getting_started = true' do
before do
alice.getting_started = true
alice.save
end
it 'does not redirect mobile users to getting_started' do
get :index, :format => :mobile
response.should_not be_redirect
end
it 'does not redirect ajax to getting_started' do
get :index, :format => :js
response.should_not be_redirect
end
end
context 'with posts in multiple aspects' do
before do
@posts = []
2.times do |n|
user = Factory(:user)
aspect = user.aspects.create(:name => 'people')
connect_users(alice, @alices_aspect_1, user, aspect)
target_aspect = n.even? ? @alices_aspect_1 : @alices_aspect_2
post = alice.post(:status_message, :text=> "hello#{n}", :to => target_aspect)
post.created_at = Time.now - (2 - n).seconds
post.save!
@posts << post
end
alice.build_comment(:text => 'lalala', :post => @posts.first).save
end
describe "post visibilities" do
before do
aspect_to_post = bob.aspects.where(:name => "generic").first
@status = bob.post(:status_message, :text=> "hello", :to => aspect_to_post)
@vis = @status.post_visibilities.first
end
it "pulls back non hidden posts" do
get :index
assigns[:posts].include?(@status).should be_true
end
it "does not pull back hidden posts" do
@vis.update_attributes(:hidden => true)
get :index
assigns[:posts].include?(@status).should be_false
end
end
describe 'infinite scroll' do
it 'renders with the infinite scroll param' do
get :index, :only_posts => true
assigns[:posts].include?(@posts.first).should be_true
response.should be_success
end
end
describe "ordering" do
it "orders posts by updated_at by default" do
get :index
assigns(:posts).should == @posts
end
it "orders posts by created_at on request" do
get :index, :sort_order => 'created_at'
assigns(:posts).should == @posts.reverse
end
it "remembers your sort order and lets you override the memory" do
get :index, :sort_order => "created_at"
assigns(:posts).should == @posts.reverse
get :index
assigns(:posts).should == @posts.reverse
get :index, :sort_order => "updated_at"
assigns(:posts).should == @posts
end
it "doesn't allow SQL injection" do
get :index, :sort_order => "\"; DROP TABLE users;"
assigns(:posts).should == @posts
get :index, :sort_order => "created_at"
assigns(:posts).should == @posts.reverse
end
end
it "returns all posts by default" do
alice.aspects.reload
get :index
assigns(:posts).length.should == 2
end
it "posts include reshares" do
reshare = alice.post(:reshare, :public => true, :root_guid => Factory(:status_message, :public => true).guid, :to => alice.aspects)
get :index
assigns[:posts].map { |x| x.id }.should include(reshare.id)
end
it "can filter to a single aspect" do
get :index, :a_ids => [@alices_aspect_2.id.to_s]
assigns(:posts).length.should == 1
end
it "can filter to multiple aspects" do
get :index, :a_ids => [@alices_aspect_1.id.to_s, @alices_aspect_2.id.to_s]
assigns(:posts).length.should == 2
end
end
describe 'performance', :performance => true do
before do
require 'benchmark'

View file

@ -0,0 +1,122 @@
require 'aspect_stream'
describe AspectStream do
describe '#aspects' do
it 'queries the user given initialized aspect ids' do
alice = stub.as_null_object
stream = AspectStream.new(alice, [1,2,3])
alice.aspects.should_receive(:where)
stream.aspects
end
it "returns all the user's aspects if no aspect ids are specified" do
alice = stub.as_null_object
stream = AspectStream.new(alice, [])
alice.aspects.should_not_receive(:where)
stream.aspects
end
it 'filters aspects given a user' do
alice = stub(:aspects => [stub(:id => 1)])
alice.aspects.stub(:where).and_return(alice.aspects)
stream = AspectStream.new(alice, [1,2,3])
stream.aspects.should == alice.aspects
end
end
describe '#aspect_ids' do
it 'maps ids from aspects' do
alice = stub.as_null_object
aspects = stub.as_null_object
stream = AspectStream.new(alice, [1,2])
stream.should_receive(:aspects).and_return(aspects)
aspects.should_receive(:map)
stream.aspect_ids
end
end
describe '#posts' do
before do
@alice = stub.as_null_object
end
it 'calls visible posts for the given user' do
stream = AspectStream.new(@alice, [1,2])
@alice.should_receive(:visible_posts).and_return(stub.as_null_object)
stream.posts
end
it 'is called with 3 types' do
stream = AspectStream.new(@alice, [1,2], :order => 'created_at')
@alice.should_receive(:visible_posts).with(hash_including(:type=> ['StatusMessage', 'Reshare', 'ActivityStreams::Photo'])).and_return(stub.as_null_object)
stream.posts
end
it 'respects ordering' do
stream = AspectStream.new(@alice, [1,2], :order => 'created_at')
@alice.should_receive(:visible_posts).with(hash_including(:order => 'created_at DESC')).and_return(stub.as_null_object)
stream.posts
end
it 'respects max_time' do
stream = AspectStream.new(@alice, [1,2], :max_time => 123)
@alice.should_receive(:visible_posts).with(hash_including(:max_time => 123)).and_return(stub.as_null_object)
stream.posts
end
end
describe '#people' do
it 'should call Person.all_from_aspects' do
class Person ; end
alice = stub.as_null_object
aspect_ids = [1,2,3]
stream = AspectStream.new(alice, [])
stream.stub(:aspect_ids).and_return(aspect_ids)
Person.should_receive(:all_from_aspects).with(stream.aspect_ids, alice)
stream.people
end
end
describe '#aspect' do
before do
alice = stub.as_null_object
@stream = AspectStream.new(alice, [1,2])
end
it "returns an aspect if the stream is not for all the user's aspects" do
@stream.stub(:for_all_aspects?).and_return(false)
@stream.aspect.should_not be_nil
end
it "returns nothing if the stream is not for all the user's aspects" do
@stream.stub(:for_all_aspects?).and_return(true)
@stream.aspect.should be_nil
end
end
describe 'for_all_aspects?' do
before do
alice = stub.as_null_object
alice.aspects.stub(:size).and_return(2)
@stream = AspectStream.new(alice, [1,2])
end
it "is true if the count of aspect_ids is equal to the size of the user's aspect count" do
@stream.aspect_ids.stub(:length).and_return(2)
@stream.should be_for_all_aspects
end
it "is false if the count of aspect_ids is not equal to the size of the user's aspect count" do
@stream.aspect_ids.stub(:length).and_return(1)
@stream.should_not be_for_all_aspects
end
end
end

View file

@ -40,6 +40,7 @@ describe Person do
person_ids.uniq.should == person_ids
end
end
describe '.local' do
it 'returns only local people' do
Person.local =~ [@person]
@ -73,7 +74,25 @@ describe Person do
}.to raise_error ActiveRecord::RecordNotFound
end
end
describe '.all_from_aspects' do
it "pulls back the right people given all a user's aspects" do
aspect_ids = bob.aspects.map(&:id)
Person.all_from_aspects(aspect_ids, bob).map(&:id).should =~ bob.contacts.includes(:person).map{|c| c.person.id}
end
it "pulls back the right people given a subset of aspects" do
aspect_ids = bob.aspects.first.id
Person.all_from_aspects(aspect_ids, bob).map(&:id).should =~ bob.aspects.first.contacts.includes(:person).map{|c| c.person.id}
end
it "respects aspects given a user" do
aspect_ids = alice.aspects.map(&:id)
Person.all_from_aspects(aspect_ids, bob).map(&:id).should == []
end
end
end
describe "delegating" do
it "delegates last_name to the profile" do
@person.last_name.should == @person.profile.last_name

View file

@ -17,38 +17,46 @@ describe User do
public_post = alice.post(:status_message, :text => "hi", :to => @alices_aspect.id, :public => true)
alice.visible_posts.should include(public_post)
end
it "contains your non-public posts" do
private_post = alice.post(:status_message, :text => "hi", :to => @alices_aspect.id, :public => false)
alice.visible_posts.should include(private_post)
end
it "contains public posts from people you're following" do
dogs = bob.aspects.create(:name => "dogs")
bobs_public_post = bob.post(:status_message, :text => "hello", :public => true, :to => dogs.id)
alice.visible_posts.should include(bobs_public_post)
end
it "contains non-public posts from people who are following you" do
bobs_post = bob.post(:status_message, :text => "hello", :to => @bobs_aspect.id)
alice.visible_posts.should include(bobs_post)
end
it "does not contain non-public posts from aspects you're not in" do
dogs = bob.aspects.create(:name => "dogs")
invisible_post = bob.post(:status_message, :text => "foobar", :to => dogs.id)
alice.visible_posts.should_not include(invisible_post)
end
it "does not contain pending posts" do
pending_post = bob.post(:status_message, :text => "hey", :public => true, :to => @bobs_aspect.id, :pending => true)
pending_post.should be_pending
alice.visible_posts.should_not include pending_post
end
it "does not contain pending photos" do
pending_photo = bob.post(:photo, :pending => true, :user_file=> File.open(photo_fixture_name), :to => @bobs_aspect)
alice.visible_posts.should_not include pending_photo
end
it "respects the :type option" do
photo = bob.post(:photo, :pending => false, :user_file=> File.open(photo_fixture_name), :to => @bobs_aspect)
alice.visible_posts.should include(photo)
alice.visible_posts(:type => 'StatusMessage').should_not include(photo)
end
it "does not contain duplicate posts" do
bobs_other_aspect = bob.aspects.create(:name => "cat people")
bob.add_contact_to_aspect(bob.contact_for(alice.person), bobs_other_aspect)
@ -59,16 +67,35 @@ describe User do
alice.visible_posts.length.should == 1
alice.visible_posts.should include(bobs_post)
end
describe 'hidden posts' do
before do
aspect_to_post = bob.aspects.where(:name => "generic").first
@status = bob.post(:status_message, :text=> "hello", :to => aspect_to_post)
@vis = @status.post_visibilities.first
end
it "pulls back non hidden posts" do
alice.visible_posts.include?(@status).should be_true
end
it "does not pull back hidden posts" do
@vis.update_attributes(:hidden => true)
alice.visible_posts.include?(@status).should be_false
end
end
context 'with many posts' do
before do
bob.move_contact(eve.person, @bobs_aspect, bob.aspects.create(:name => 'new aspect'))
time_interval = 1000
time_past = 1000000
(1..25).each do |n|
[alice, bob, eve].each do |u|
aspect_to_post = u.aspects.where(:name => "generic").first
post = u.post :status_message, :text => "#{u.username} - #{n}", :to => aspect_to_post.id
post.created_at = post.created_at - time_interval
post.updated_at = post.updated_at - time_interval
post.created_at = (post.created_at-time_past) - time_interval
post.updated_at = (post.updated_at-time_past) + time_interval
post.save
time_interval += 1000
end
@ -79,6 +106,14 @@ describe User do
bob.visible_posts.should == bob.visible_posts(:by_members_of => bob.aspects.map { |a| a.id }) # it is the same when joining through aspects
bob.visible_posts.sort_by { |p| p.updated_at }.map { |p| p.id }.should == bob.visible_posts.map { |p| p.id }.reverse #it is sorted updated_at desc by default
# It should respect the order option
opts = {:order => 'created_at DESC'}
bob.visible_posts(opts).first.created_at.should > bob.visible_posts(opts).last.created_at
# It should respect the order option
opts = {:order => 'updated_at DESC'}
bob.visible_posts(opts).first.updated_at.should > bob.visible_posts(opts).last.updated_at
# It should respect the limit option
opts = {:limit => 40}
bob.visible_posts(opts).length.should == 40