Make Photos not inherit from Posts

This commit is contained in:
Manuel Schölling 2011-10-13 20:42:20 +02:00
parent 02a3c3f88b
commit bdeae54c6c
34 changed files with 481 additions and 331 deletions

View file

@ -104,7 +104,7 @@ GEM
xml-simple
bcrypt-ruby (2.1.4)
builder (2.1.2)
bunny (0.7.6)
bunny (0.7.8)
capistrano (2.5.19)
highline
net-scp (>= 1.0.0)
@ -128,14 +128,14 @@ GEM
erubis
extlib
highline
json (<= 1.4.6, >= 1.4.4)
json (>= 1.4.4, <= 1.4.6)
mixlib-authentication (>= 1.1.0)
mixlib-cli (>= 1.1.0)
mixlib-config (>= 1.1.2)
mixlib-log (>= 1.2.0)
moneta
ohai (>= 0.5.7)
rest-client (< 1.7.0, >= 1.0.4)
rest-client (>= 1.0.4, < 1.7.0)
uuidtools
childprocess (0.2.2)
ffi (~> 1.0.6)
@ -164,7 +164,7 @@ GEM
warden (~> 1.0.3)
devise_invitable (0.5.0)
devise (~> 1.3.1)
rails (<= 3.2, >= 3.0.0)
rails (>= 3.0.0, <= 3.2)
diff-lcs (1.1.3)
em-synchrony (0.2.0)
eventmachine (>= 0.12.9)
@ -181,7 +181,7 @@ GEM
faraday (0.6.1)
addressable (~> 2.2.4)
multipart-post (~> 1.1.0)
rack (< 2, >= 1.1.0)
rack (>= 1.1.0, < 2)
faraday-stack (0.1.3)
faraday (~> 0.6)
faraday_middleware (0.6.5)
@ -211,7 +211,7 @@ GEM
rspec-instafail (~> 0.1.8)
ruby-progressbar (~> 0.0.10)
gem_plugin (0.2.3)
gherkin (2.5.1)
gherkin (2.5.2)
json (>= 1.4.6)
haml (3.1.2)
hashie (1.0.0)
@ -281,7 +281,7 @@ GEM
net-ssh (2.0.24)
net-ssh-gateway (1.1.0)
net-ssh (>= 1.99.1)
newrelic_rpm (3.1.2)
newrelic_rpm (3.2.0)
nokogiri (1.4.3.1)
oa-basic (0.2.6)
oa-core (= 0.2.6)
@ -318,7 +318,7 @@ GEM
addressable (~> 2.2)
ohai (0.5.8)
extlib
json (<= 1.4.6, >= 1.4.4)
json (>= 1.4.4, <= 1.4.6)
mixlib-cli
mixlib-config
mixlib-log
@ -366,7 +366,8 @@ GEM
rake (0.9.2)
rash (0.3.0)
hashie (~> 1.0.0)
rdoc (3.9.4)
rdoc (3.10)
json (~> 1.4)
redcarpet (2.0.0b5)
redis (2.2.2)
redis-namespace (0.8.0)
@ -434,7 +435,7 @@ GEM
sqlite3 (1.3.4)
subexec (0.0.4)
systemu (2.4.0)
term-ansicolor (1.0.6)
term-ansicolor (1.0.7)
thin (1.2.11)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
@ -459,13 +460,13 @@ GEM
uuidtools (2.1.2)
vegas (0.1.8)
rack (>= 1.0.0)
warden (1.0.5)
warden (1.0.6)
rack (>= 1.0)
webmock (1.6.2)
addressable (>= 2.2.2)
crack (>= 0.1.7)
will_paginate (3.0.pre2)
xml-simple (1.1.0)
xml-simple (1.1.1)
yard (0.7.2)
yui-compressor (0.9.6)
POpen4 (>= 0.1.4)

View file

@ -48,7 +48,7 @@ class ActivityStreams::PhotosController < ApplicationController
end
def show
@photo = current_user.find_visible_post_by_id(params[:id])
@photo = current_user.find_visible_shareable_by_id(Photo, params[:id])
respond_with @photo
end

View file

@ -14,7 +14,7 @@ class CommentsController < ApplicationController
end
def create
target = current_user.find_visible_post_by_id params[:post_id]
target = current_user.find_visible_shareable_by_id Post, params[:post_id]
text = params[:text]
if target
@ -55,7 +55,7 @@ class CommentsController < ApplicationController
end
def index
@post = current_user.find_visible_post_by_id(params[:post_id])
@post = current_user.find_visible_shareable_by_id(Post, params[:post_id])
if @post
@comments = @post.comments.includes(:author => :profile).order('created_at ASC')
render :layout => false

View file

@ -59,10 +59,10 @@ class LikesController < ApplicationController
def target
@target ||= if params[:post_id]
current_user.find_visible_post_by_id(params[:post_id])
current_user.find_visible_shareable_by_id(Post, params[:post_id])
else
comment = Comment.find(params[:comment_id])
comment = nil unless current_user.find_visible_post_by_id(comment.commentable_id)
comment = nil unless current_user.find_visible_shareable_by_id(Post, comment.commentable_id)
comment
end
end

View file

@ -29,7 +29,7 @@ class PhotosController < ApplicationController
@contacts_of_contact_count = 0
end
@posts = current_user.posts_from(@person).where(:type => 'Photo').paginate(:page => params[:page])
@posts = current_user.photos_from(@person).paginate(:page => params[:page])
render 'people/show'
@ -118,7 +118,7 @@ class PhotosController < ApplicationController
end
def destroy
photo = current_user.posts.where(:id => params[:id]).first
photo = current_user.photos.where(:id => params[:id]).first
if photo
current_user.retract(photo)
@ -148,7 +148,7 @@ class PhotosController < ApplicationController
end
def edit
if @photo = current_user.posts.where(:id => params[:id]).first
if @photo = current_user.photos.where(:id => params[:id]).first
respond_with @photo
else
redirect_to person_photos_path(current_user.person)
@ -156,7 +156,7 @@ class PhotosController < ApplicationController
end
def update
photo = current_user.posts.where(:id => params[:id]).first
photo = current_user.photos.where(:id => params[:id]).first
if photo
if current_user.update_post( photo, params[:photo] )
flash.now[:notice] = I18n.t 'photos.update.notice'
@ -187,7 +187,7 @@ class PhotosController < ApplicationController
end
def photo
@photo ||= current_user.find_visible_post_by_id(params[:id], :type => 'Photo')
@photo ||= current_user.find_visible_shareable_by_id(Photo, params[:id])
end
def additional_photos

View file

@ -18,7 +18,7 @@ class PostsController < ApplicationController
key = params[:id].to_s.length <= 8 ? :id : :guid
if user_signed_in?
@post = current_user.find_visible_post_by_id(params[:id], :key => key)
@post = current_user.find_visible_shareable_by_id(Post, params[:id], :key => key)
else
@post = Post.where(key => params[:id], :public => true).includes(:author, :comments => :author).first
end

View file

@ -10,6 +10,7 @@ class Aspect < ActiveRecord::Base
has_many :aspect_visibilities
has_many :posts, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Post'
has_many :photos, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Photo'
validates :name, :presence => true, :length => { :maximum => 20 }
@ -24,5 +25,17 @@ class Aspect < ActiveRecord::Base
def to_s
name
end
def << (shareable)
case shareable
when Post
self.posts << shareable
when Photo
self.photos << shareable
else
raise "Unknown shareable type '#{shareable.class.base_class.to_s}'"
end
end
end

View file

@ -54,9 +54,9 @@ class Contact < ActiveRecord::Base
:into => aspects.first)
end
def receive_post(post)
ShareVisibility.create!(:shareable_id => post.id, :shareable_type => 'Post', :contact_id => self.id)
post.socket_to_user(self.user, :aspect_ids => self.aspect_ids) if post.respond_to? :socket_to_user
def receive_shareable(shareable)
ShareVisibility.create!(:shareable_id => shareable.id, :shareable_type => shareable.class.base_class.to_s, :contact_id => self.id)
shareable.socket_to_user(self.user, :aspect_ids => self.aspect_ids) if shareable.respond_to? :socket_to_user
end
def contacts

View file

@ -28,6 +28,7 @@ class Person < ActiveRecord::Base
has_many :contacts, :dependent => :destroy # Other people's contacts for this person
has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
has_many :photos, :foreign_key => :author_id, :dependent => :destroy # This person's own photos
has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments
belongs_to :owner, :class_name => 'User'
@ -249,7 +250,7 @@ class Person < ActiveRecord::Base
end
def has_photos?
self.posts.where(:type => "Photo").exists?
self.photos.exists?
end
def as_json( opts = {} )

View file

@ -2,8 +2,12 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class Photo < Post
class Photo < ActiveRecord::Base
require 'carrierwave/orm/activerecord'
include Diaspora::Commentable
include Diaspora::Shareable
mount_uploader :processed_image, ProcessedImage
mount_uploader :unprocessed_image, UnprocessedImage
@ -40,7 +44,12 @@ class Photo < Post
end
def self.diaspora_initialize(params = {})
photo = super(params)
photo = self.new params.to_hash
photo.author = params[:author]
photo.public = params[:public] if params[:public]
photo.pending = params[:pending] if params[:pending]
photo.diaspora_handle = photo.author.diaspora_handle
image_file = params.delete(:user_file)
photo.random_string = ActiveSupport::SecureRandom.hex(10)
photo.unprocessed_image.store! image_file

View file

@ -3,20 +3,13 @@
# the COPYRIGHT file.
class Post < ActiveRecord::Base
require File.join(Rails.root, 'lib/diaspora/web_socket')
include ApplicationHelper
include ROXML
include Diaspora::Webhooks
include Diaspora::Guid
include Diaspora::Likeable
include Diaspora::Commentable
include Diaspora::Shareable
xml_attr :diaspora_handle
xml_attr :provider_display_name
xml_attr :public
xml_attr :created_at
has_many :mentions, :dependent => :destroy
@ -25,14 +18,9 @@ class Post < ActiveRecord::Base
belongs_to :o_embed_cache
belongs_to :author, :class_name => 'Person'
validates :guid, :uniqueness => true
after_create :cache_for_author
#scopes
scope :all_public, where(:public => true, :pending => false)
scope :includes_for_a_stream, includes(:o_embed_cache, {:author => :profile}, :mentions => {:person => :profile}) #note should include root and photos, but i think those are both on status_message
def self.for_a_stream(max_time, order)
@ -47,27 +35,6 @@ class Post < ActiveRecord::Base
end
#############
def diaspora_handle
read_attribute(:diaspora_handle) || self.author.diaspora_handle
end
def user_refs
if AspectVisibility.exists?(:shareable_id => self.id, :shareable_type => 'Post')
self.share_visibilities.count + 1
else
self.share_visibilities.count
end
end
def reshare_count
@reshare_count ||= Post.where(:root_guid => self.guid).count
end
def diaspora_handle= nd
self.author = Person.where(:diaspora_handle => nd).first
write_attribute(:diaspora_handle, nd)
end
def self.diaspora_initialize params
new_post = self.new params.to_hash
new_post.author = params[:author]
@ -77,23 +44,15 @@ class Post < ActiveRecord::Base
new_post
end
def reshare_count
@reshare_count ||= Post.where(:root_guid => self.guid).count
end
# @return Returns true if this Post will accept updates (i.e. updates to the caption of a photo).
def mutable?
false
end
# The list of people that should receive this Post.
#
# @param [User] user The context, or dispatching user.
# @return [Array<Person>] The list of subscribers to this post
def subscribers(user)
if self.public?
user.contact_people
else
user.people_in_aspects(user.aspects_with_post(self.id))
end
end
def activity_streams?
false
end
@ -106,12 +65,6 @@ class Post < ActiveRecord::Base
I18n.t('notifier.a_post_you_shared')
end
# @return [Integer]
def update_comments_counter
self.class.where(:id => self.id).
update_all(:comments_count => self.comments.count)
end
# @return [Boolean]
def cache_for_author
if self.should_cache_for_author?
@ -126,72 +79,4 @@ class Post < ActiveRecord::Base
self.triggers_caching? && RedisCache.configured? &&
RedisCache.acceptable_types.include?(self.type) && user = self.author.owner
end
# @param [User] user The user that is receiving this post.
# @param [Person] person The person who dispatched this post to the
# @return [void]
def receive(user, person)
#exists locally, but you dont know about it
#does not exsist locally, and you dont know about it
#exists_locally?
#you know about it, and it is mutable
#you know about it, and it is not mutable
self.class.transaction do
local_post = persisted_post
if local_post && verify_persisted_post(local_post)
self.receive_persisted(user, person, local_post)
elsif !local_post
self.receive_non_persisted(user, person)
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from post owner' existing_post=#{self.id}")
false
end
end
end
protected
# @return [Post,void]
def persisted_post
self.class.where(:guid => self.guid).first
end
# @return [Boolean]
def verify_persisted_post(persisted_post)
persisted_post.author_id == self.author_id
end
def receive_persisted(user, person, local_post)
known_post = user.find_visible_post_by_id(self.guid, :key => :guid)
if known_post
if known_post.mutable?
known_post.update_attributes(self.attributes)
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_post=#{known_post.id}")
false
end
else
user.contact_for(person).receive_post(local_post)
user.notify_if_mentioned(local_post)
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_post=#{local_post.id}")
true
end
end
def receive_non_persisted(user, person)
if self.save
user.contact_for(person).receive_post(self)
user.notify_if_mentioned(self)
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}")
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}")
false
end
end
end

View file

@ -15,7 +15,7 @@ class Retraction
def subscribers(user)
unless self.type == 'Person'
@subscribers ||= self.object.subscribers(user)
@subscribers -= self.object.resharers
@subscribers -= self.object.resharers unless self.object.is_a?(Photo)
@subscribers
else
raise 'HAX: you must set the subscribers manaully before unfriending' if @subscribers.nil?

View file

@ -30,7 +30,7 @@ class User < ActiveRecord::Base
validates_associated :person
has_one :person, :foreign_key => :owner_id
delegate :public_key, :posts, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :to => :person
delegate :public_key, :posts, :photos, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :to => :person
has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id, :dependent => :destroy
has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id, :dependent => :destroy
@ -207,7 +207,7 @@ class User < ActiveRecord::Base
def add_to_streams(post, aspects_to_insert)
post.socket_to_user(self, :aspect_ids => aspects_to_insert.map{|x| x.id}) if post.respond_to? :socket_to_user
aspects_to_insert.each do |aspect|
aspect.posts << post
aspect << post
end
end

View file

@ -1,41 +0,0 @@
class ShareAnything < ActiveRecord::Migration
def self.up
remove_foreign_key :aspect_visibilities, :posts
remove_index :aspect_visibilities, :post_id_and_aspect_id
remove_index :aspect_visibilities, :post_id
change_table :aspect_visibilities do |t|
t.rename :post_id, :shareable_id
t.string :shareable_type, :default => 'Post', :null => false
end
remove_foreign_key :post_visibilities, :posts
remove_index :post_visibilities, :contact_id_and_post_id
remove_index :post_visibilities, :post_id_and_hidden_and_contact_id
change_table :post_visibilities do |t|
t.rename :post_id, :shareable_id
t.string :shareable_type, :default => 'Post', :null => false
end
rename_table :post_visibilities, :share_visibilities
end
def self.down
rename_column :aspect_visibilities, :shareable_id, :post_id
add_foreign_key :aspect_visibilities, :posts
add_index :aspect_visibilities, :post_id
remove_column :aspect_visibilities, :shareable_type
rename_table :share_visibilities, :post_visibilities
rename_column :post_visibilities, :shareable_id, :post_id
add_foreign_key :post_visibilities, :posts
add_index :post_visibilities, :post_id_and_post_id
add_index :post_visibilities, [:contact_id, :post_id]
add_index :post_visibilities, [:post_id, :hidden, :contact_id]
add_index :post_visibilities, :post_id
remove_column :post_visibilities, :shareable_type
end
end

View file

@ -0,0 +1,58 @@
class ShareAnything < ActiveRecord::Migration
def self.up
remove_foreign_key :aspect_visibilities, :posts
remove_index :aspect_visibilities, :post_id_and_aspect_id
remove_index :aspect_visibilities, :post_id
change_table :aspect_visibilities do |t|
t.rename :post_id, :shareable_id
t.string :shareable_type, :default => 'Post', :null => false
end
add_index :aspect_visibilities, [:shareable_id, :shareable_type, :aspect_id], :name => 'shareable_and_aspect_id'
add_index :aspect_visibilities, [:shareable_id, :shareable_type]
remove_foreign_key :post_visibilities, :posts
remove_index :post_visibilities, :contact_id_and_post_id
remove_index :post_visibilities, :post_id_and_hidden_and_contact_id
change_table :post_visibilities do |t|
t.rename :post_id, :shareable_id
t.string :shareable_type, :default => 'Post', :null => false
end
rename_table :post_visibilities, :share_visibilities
add_index :share_visibilities, [:shareable_id, :shareable_type, :contact_id], :name => 'shareable_and_contact_id'
add_index :share_visibilities, [:shareable_id, :shareable_type, :hidden, :contact_id], :name => 'shareable_and_hidden_and_contact_id'
end
def self.down
remove_index :share_visibilities, :name => 'shareable_and_hidden_and_contact_id'
remove_index :share_visibilities, :name => 'shareable_and_contact_id'
rename_table :share_visibilities, :post_visibilities
change_table :post_visibilities do |t|
t.remove :shareable_type
t.rename :shareable_id, :post_id
end
add_index :post_visibilities, [:post_id, :hidden, :contact_id]
add_index :post_visibilities, [:contact_id, :post_id]
add_foreign_key :post_visibilities, :posts
remove_index :aspect_visibilities, [:shareable_id, :shareable_type]
remove_index :aspect_visibilities, :name => 'shareable_and_aspect_id'
change_table :aspect_visibilities do |t|
t.remove :shareable_type
t.rename :shareable_id, :post_id
end
add_index :aspect_visibilities, :post_id
add_index :aspect_visibilities, [:post_id, :aspect_id]
add_foreign_key :aspect_visibilities, :posts
end
end

View file

@ -0,0 +1,68 @@
class MovePhotosToTheirOwnTable < ActiveRecord::Migration
def self.up
create_table "photos", :force => true do |t|
t.integer "author_id", :null => false
t.boolean "public", :default => false, :null => false
t.string "diaspora_handle"
t.string "guid", :null => false
t.boolean "pending", :default => false, :null => false
t.text "text"
t.text "remote_photo_path"
t.string "remote_photo_name"
t.string "random_string"
t.string "processed_image"
t.datetime "created_at"
t.datetime "updated_at"
t.string "unprocessed_image"
t.string "status_message_guid"
t.integer "comments_count"
end
execute <<SQL
INSERT INTO photos
SELECT id, author_id, public, diaspora_handle, guid, pending, text, remote_photo_path, remote_photo_name, random_string, processed_image,
created_at, updated_at, unprocessed_image, status_message_guid, comments_count
FROM posts
WHERE type = 'Photo'
SQL
execute "UPDATE aspect_visibilities AS av, photos SET av.shareable_type='Photo' WHERE av.shareable_id=photos.id"
execute "UPDATE share_visibilities AS sv, photos SET sv.shareable_type='Photo' WHERE sv.shareable_id=photos.id"
# all your base are belong to us!
execute "DELETE FROM posts WHERE type='Photo'"
end
def self.down
execute <<SQL
INSERT INTO posts
SELECT NULL AS id, author_id, public, diaspora_handle, guid, pending, 'Photo' AS type, text, remote_photo_path, remote_photo_name, random_string,
processed_image, NULL AS youtube_titles, created_at, updated_at, unprocessed_image, NULL AS object_url, NULL AS image_url, NULL AS image_height, NULL AS image_width, NULL AS provider_display_name,
NULL AS actor_url, NULL AS objectId, NULL AS root_guid, status_message_guid, 0 AS likes_count, comments_count, NULL AS o_embed_cache_id
FROM photos
SQL
execute <<SQL
UPDATE aspect_visibilities, posts, photos
SET
aspect_visibilities.shareable_id=posts.id,
aspect_visibilities.shareable_type='Post'
WHERE
posts.guid=photos.guid AND
photos.id=aspect_visibilities.shareable_id
SQL
execute <<SQL
UPDATE share_visibilities, posts, photos
SET
share_visibilities.shareable_id=posts.id,
share_visibilities.shareable_type='Post'
WHERE
posts.guid=photos.guid AND
photos.id=share_visibilities.shareable_id
SQL
execute "DROP TABLE photos"
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 => 20111011193702) do
ActiveRecord::Schema.define(:version => 20111012215141) do
create_table "aspect_memberships", :force => true do |t|
t.integer "aspect_id", :null => false
@ -32,6 +32,8 @@ ActiveRecord::Schema.define(:version => 20111011193702) do
end
add_index "aspect_visibilities", ["aspect_id"], :name => "index_aspect_visibilities_on_aspect_id"
add_index "aspect_visibilities", ["shareable_id", "shareable_type", "aspect_id"], :name => "shareable_and_aspect_id"
add_index "aspect_visibilities", ["shareable_id", "shareable_type"], :name => "index_aspect_visibilities_on_shareable_id_and_shareable_type"
create_table "aspects", :force => true do |t|
t.string "name", :null => false
@ -242,6 +244,24 @@ ActiveRecord::Schema.define(:version => 20111011193702) do
add_index "people", ["guid"], :name => "index_people_on_guid", :unique => true
add_index "people", ["owner_id"], :name => "index_people_on_owner_id", :unique => true
create_table "photos", :force => true do |t|
t.integer "author_id", :null => false
t.boolean "public", :default => false, :null => false
t.string "diaspora_handle"
t.string "guid", :null => false
t.boolean "pending", :default => false, :null => false
t.text "text"
t.text "remote_photo_path"
t.string "remote_photo_name"
t.string "random_string"
t.string "processed_image"
t.datetime "created_at"
t.datetime "updated_at"
t.string "unprocessed_image"
t.string "status_message_guid"
t.integer "comments_count"
end
create_table "pods", :force => true do |t|
t.string "host"
t.boolean "ssl"
@ -350,6 +370,8 @@ ActiveRecord::Schema.define(:version => 20111011193702) do
end
add_index "share_visibilities", ["contact_id"], :name => "index_post_visibilities_on_contact_id"
add_index "share_visibilities", ["shareable_id", "shareable_type", "contact_id"], :name => "shareable_and_contact_id"
add_index "share_visibilities", ["shareable_id", "shareable_type", "hidden", "contact_id"], :name => "shareable_and_hidden_and_contact_id"
add_index "share_visibilities", ["shareable_id"], :name => "index_post_visibilities_on_post_id"
create_table "tag_followings", :force => true do |t|

View file

@ -28,7 +28,7 @@ module NavigationHelpers
when /^my account settings page$/
edit_user_path
when /^the photo page for "([^\"]*)"'s latest post$/
photo_path(User.find_by_email($1).posts.where(:type => "Photo").last)
photo_path(User.find_by_email($1).photos.last)
when /^the photo page for "([^\"]*)"'s post "([^\"]*)"$/
photo_path(User.find_by_email($1).posts.find_by_text($2))
when /^"(\/.*)"/

View file

@ -5,7 +5,7 @@ module PhotoMover
FileUtils::mkdir_p temp_dir
Dir.chdir 'tmp/exports'
photos = user.visible_posts.where(:author_id => user.person.id, :type => 'Photo')
photos = user.visible_shareables(Post).where(:author_id => user.person.id, :type => 'Photo')
photos_dir = "#{user.id}/photos"
FileUtils::mkdir_p photos_dir

View file

@ -14,12 +14,12 @@ module Diaspora
def last_three_comments
self.comments.order('created_at DESC').limit(3).includes(:author => :profile).reverse
end
end
# @return [Integer]
def update_comments_counter
self.class.where(:id => self.id).
update_all(:comments_count => self.comments.count)
end
# @return [Integer]
def update_comments_counter
self.class.where(:id => self.id).
update_all(:comments_count => self.comments.count)
end
end
end

View file

@ -64,7 +64,7 @@ module Diaspora
}
xml.posts {
user.visible_posts.find_all_by_author_id(user_person_id).each do |post|
user.visible_shareables(Post).find_all_by_author_id(user_person_id).each do |post|
#post.comments.each do |comment|
# post_doc << comment.to_xml
#end

View file

@ -50,7 +50,7 @@ class RedisCache
:order => self.order
})
sql = @user.visible_posts_sql(opts)
sql = @user.visible_shareables_sql(Post, opts)
hashes = Post.connection.select_all(sql)
# hashes are inserted into set in a single transaction

View file

@ -4,14 +4,129 @@
module Diaspora
module Shareable
require File.join(Rails.root, 'lib/diaspora/web_socket')
include Diaspora::Webhooks
def self.included(model)
model.instance_eval do
include ROXML
include Diaspora::Guid
has_many :aspect_visibilities, :as => :shareable
has_many :aspects, :through => :aspect_visibilities
has_many :share_visibilities, :as => :shareable
has_many :contacts, :through => :share_visibilities
belongs_to :author, :class_name => 'Person'
validates :guid, :uniqueness => true
#scopes
scope :all_public, where(:public => true, :pending => false)
xml_attr :diaspora_handle
xml_attr :public
xml_attr :created_at
end
def diaspora_handle
read_attribute(:diaspora_handle) || self.author.diaspora_handle
end
def diaspora_handle= nd
self.author = Person.where(:diaspora_handle => nd).first
write_attribute(:diaspora_handle, nd)
end
def user_refs
if AspectVisibility.exists?(:shareable_id => self.id, :shareable_type => self.class.base_class.to_s)
self.share_visibilities.count + 1
else
self.share_visibilities.count
end
end
# @param [User] user The user that is receiving this shareable.
# @param [Person] person The person who dispatched this shareable to the
# @return [void]
def receive(user, person)
#exists locally, but you dont know about it
#does not exsist locally, and you dont know about it
#exists_locally?
#you know about it, and it is mutable
#you know about it, and it is not mutable
self.class.transaction do
local_shareable = persisted_shareable
if local_shareable && verify_persisted_shareable(local_shareable)
self.receive_persisted(user, person, local_shareable)
elsif !local_shareable
self.receive_non_persisted(user, person)
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason='update not from shareable owner' existing_shareable=#{self.id}")
false
end
end
end
# The list of people that should receive this Shareable.
#
# @param [User] user The context, or dispatching user.
# @return [Array<Person>] The list of subscribers to this shareable
def subscribers(user)
if self.public?
user.contact_people
else
user.people_in_aspects(user.aspects_with_shareable(self.class, self.id))
end
end
protected
# @return [Shareable,void]
def persisted_shareable
self.class.where(:guid => self.guid).first
end
# @return [Boolean]
def verify_persisted_shareable(persisted_shareable)
persisted_shareable.author_id == self.author_id
end
def receive_persisted(user, person, local_shareable)
known_shareable = user.find_visible_shareable_by_id(self.class.base_class, self.guid, :key => :guid)
if known_shareable
if known_shareable.mutable?
known_shareable.update_attributes(self.attributes)
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=abort sender=#{self.diaspora_handle} reason=immutable") #existing_shareable=#{known_shareable.id}")
false
end
else
user.contact_for(person).receive_shareable(local_shareable)
user.notify_if_mentioned(local_shareable)
Rails.logger.info("event=receive payload_type=#{self.class} update=true status=complete sender=#{self.diaspora_handle}") #existing_shareable=#{local_shareable.id}")
true
end
end
def receive_non_persisted(user, person)
if self.save
user.contact_for(person).receive_shareable(self)
user.notify_if_mentioned(self)
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=complete sender=#{self.diaspora_handle}")
true
else
Rails.logger.info("event=receive payload_type=#{self.class} update=false status=abort sender=#{self.diaspora_handle} reason=#{self.errors.full_messages}")
false
end
end
end
end
end

View file

@ -8,67 +8,76 @@ module Diaspora
module UserModules
module Querying
def find_visible_post_by_id( id, opts={} )
def find_visible_shareable_by_id(klass, id, opts={} )
key = opts.delete(:key) || :id
post = Post.where(key => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).select("posts.*").first
post ||= Post.where(key => id, :author_id => self.person.id).where(opts).first
post ||= Post.where(key => id, :public => true).where(opts).first
post = klass.where(key => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).select(klass.table_name+".*").first
post ||= klass.where(key => id, :author_id => self.person.id).where(opts).first
post ||= klass.where(key => id, :public => true).where(opts).first
end
def visible_posts(opts={})
opts = prep_opts(opts)
post_ids = visible_post_ids(opts)
Post.where(:id => post_ids).select('DISTINCT posts.*').limit(opts[:limit]).order(opts[:order_with_table])
def visible_shareables(klass, opts={})
opts = prep_opts(klass, opts)
shareable_ids = visible_shareable_ids(klass, opts)
klass.where(:id => shareable_ids).select('DISTINCT '+klass.to_s.tableize+'.*').limit(opts[:limit]).order(opts[:order_with_table])
end
def visible_post_ids(opts={})
opts = prep_opts(opts)
def visible_shareable_ids(klass, opts={})
opts = prep_opts(klass, opts)
if RedisCache.configured? && RedisCache.supported_order?(opts[:order_field]) && opts[:all_aspects?].present?
cache = RedisCache.new(self, opts[:order_field])
cache.ensure_populated!(opts)
post_ids = cache.post_ids(opts[:max_time], opts[:limit])
name = klass.to_s.downcase
shareable_ids = cache.send(name+"_ids", opts[:max_time], opts[:limit])
end
if post_ids.blank? || post_ids.length < opts[:limit]
visible_ids_from_sql(opts)
if shareable_ids.blank? || shareable_ids.length < opts[:limit]
visible_ids_from_sql(klass, opts)
else
post_ids
shareable_ids
end
end
# @return [Array<Integer>]
def visible_ids_from_sql(opts={})
opts = prep_opts(opts)
Post.connection.select_values(visible_posts_sql(opts)).map { |id| id.to_i }
def visible_ids_from_sql(klass, opts={})
opts = prep_opts(klass, opts)
klass.connection.select_values(visible_shareable_sql(klass, opts)).map { |id| id.to_i }
end
def visible_posts_sql(opts={})
opts = prep_opts(opts)
select_clause ='DISTINCT posts.id, posts.updated_at AS updated_at, posts.created_at AS created_at'
def visible_shareable_sql(klass, opts={})
table = klass.table_name
opts = prep_opts(klass, opts)
select_clause ='DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at' % [klass.table_name, klass.table_name, klass.table_name]
posts_from_others = Post.joins(:contacts).where( :pending => false, :type => opts[:type], :share_visibilities => {:hidden => opts[:hidden]}, :contacts => {:user_id => self.id})
posts_from_self = self.person.posts.where(:pending => false, :type => opts[:type])
conditions = {:pending => false, :share_visibilities => {:hidden => opts[:hidden]}, :contacts => {:user_id => self.id} }
conditions[:type] = opts[:type] if opts.has_key?(:type)
shareable_from_others = klass.joins(:contacts).where(conditions)
conditions = {:pending => false }
conditions[:type] = opts[:type] if opts.has_key?(:type)
shareable_from_self = self.person.send(klass.to_s.tableize).where(conditions)
if opts[:by_members_of]
posts_from_others = posts_from_others.joins(:contacts => :aspect_memberships).where(
shareable_from_others = shareable_from_others.joins(:contacts => :aspect_memberships).where(
:aspect_memberships => {:aspect_id => opts[:by_members_of]})
posts_from_self = posts_from_self.joins(:aspect_visibilities).where(:aspect_visibilities => {:aspect_id => opts[:by_members_of]})
shareable_from_self = shareable_from_self.joins(:aspect_visibilities).where(:aspect_visibilities => {:aspect_id => opts[:by_members_of]})
end
posts_from_others = posts_from_others.select(select_clause).order(opts[:order_with_table]).where(Post.arel_table[opts[:order_field]].lt(opts[:max_time]))
posts_from_self = posts_from_self.select(select_clause).order(opts[:order_with_table]).where(Post.arel_table[opts[:order_field]].lt(opts[:max_time]))
shareable_from_others = shareable_from_others.select(select_clause).order(opts[:order_with_table]).where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
shareable_from_self = shareable_from_self.select(select_clause).order(opts[:order_with_table]).where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
"(#{posts_from_others.to_sql} LIMIT #{opts[:limit]}) UNION ALL (#{posts_from_self.to_sql} LIMIT #{opts[:limit]}) ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
"(#{shareable_from_others.to_sql} LIMIT #{opts[:limit]}) UNION ALL (#{shareable_from_self.to_sql} LIMIT #{opts[:limit]}) ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
end
def contact_for(person)
return nil unless person
contact_for_person_id(person.id)
end
def aspects_with_post(post_id)
self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => post_id, :shareable_type => 'Post'})
def aspects_with_shareable(base_class_name_or_class, shareable_id)
base_class_name = base_class_name_or_class
base_class_name = base_class_name_or_class.base_class.to_s if base_class_name_or_class.is_a?(Class)
self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => shareable_id, :shareable_type => base_class_name})
end
def contact_for_person_id(person_id)
@ -105,36 +114,44 @@ module Diaspora
end
def posts_from(person)
return self.person.posts.where(:pending => false).order("created_at DESC") if person == self.person
self.shareables_from(Post, person)
end
def photos_from(person)
self.shareables_from(Photo, person)
end
def shareables_from(klass, person)
return self.person.send(klass.table_name).where(:pending => false).order("created_at DESC") if person == self.person
con = Contact.arel_table
p = Post.arel_table
post_ids = []
p = klass.arel_table
shareable_ids = []
if contact = self.contact_for(person)
post_ids = Post.connection.select_values(
contact.share_visibilities.where(:hidden => false, :shareable_type => 'Post').select('share_visibilities.shareable_id').to_sql
shareable_ids = klass.connection.select_values(
contact.share_visibilities.where(:hidden => false, :shareable_type => klass.to_s).select('share_visibilities.shareable_id').to_sql
)
end
post_ids += Post.connection.select_values(
person.posts.where(:public => true).select('posts.id').to_sql
shareable_ids += klass.connection.select_values(
person.send(klass.table_name).where(:public => true).select(klass.table_name+'.id').to_sql
)
Post.where(:id => post_ids, :pending => false).select('DISTINCT posts.*').order("posts.created_at DESC")
klass.where(:id => shareable_ids, :pending => false).select('DISTINCT '+klass.table_name+'.*').order(klass.table_name+".created_at DESC")
end
protected
# @return [Hash]
def prep_opts(opts)
def prep_opts(klass, opts)
defaults = {
:type => Stream::Base::TYPES_OF_POST_IN_STREAM,
:order => 'created_at DESC',
:limit => 15,
:hidden => false
}
defaults[:type] = Stream::Base::TYPES_OF_POST_IN_STREAM if klass == Post
opts = defaults.merge(opts)
opts[:order_field] = opts[:order].split.first.to_sym
opts[:order_with_table] = 'posts.' + opts[:order]
opts[:order_with_table] = klass.table_name + '.' + opts[:order]
opts[:max_time] = Time.at(opts[:max_time]) if opts[:max_time].is_a?(Integer)
opts[:max_time] ||= Time.now + 1

View file

@ -37,12 +37,12 @@ class Stream::Aspect < Stream::Base
# @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(:all_aspects? => for_all_aspects?,
:by_members_of => aspect_ids,
:type => TYPES_OF_POST_IN_STREAM,
:order => "#{order} DESC",
:max_time => max_time
# NOTE(this should be something like Post.all_for_stream(@user, aspect_ids, {}) that calls visible_shareables
@posts ||= user.visible_shareables(Post, :all_aspects? => for_all_aspects?,
:by_members_of => aspect_ids,
:type => TYPES_OF_POST_IN_STREAM,
:order => "#{order} DESC",
:max_time => max_time
).for_a_stream(max_time, order)
end

View file

@ -25,7 +25,7 @@ describe "attack vectors" do
zord.perform!
}.should raise_error /not a valid object/
bob.visible_posts.include?(post_from_non_contact).should be_false
bob.visible_shareables(Post).include?(post_from_non_contact).should be_false
Post.count.should == post_count
end
end
@ -42,7 +42,7 @@ describe "attack vectors" do
zord.perform!
}.should raise_error /not a valid object/
alice.reload.visible_posts.should_not include(StatusMessage.find(original_message.id))
alice.reload.visible_shareables(Post).should_not include(StatusMessage.find(original_message.id))
end
context 'malicious contact attack vector' do
@ -78,11 +78,11 @@ describe "attack vectors" do
zord.perform!
}.should_not change{
bob.reload.visible_posts.count
bob.reload.visible_shareables(Post).count
}
original_message.reload.text.should == "store this!"
bob.visible_posts.first.text.should == "store this!"
bob.visible_shareables(Post).first.text.should == "store this!"
end
end
@ -111,7 +111,7 @@ describe "attack vectors" do
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform!
bob.visible_posts.count.should == 1
bob.visible_shareables(Post).count.should == 1
StatusMessage.count.should == 1
ret = Retraction.new
@ -124,7 +124,7 @@ describe "attack vectors" do
zord.perform!
StatusMessage.count.should == 1
bob.visible_posts.count.should == 1
bob.visible_shareables(Post).count.should == 1
end
it "disregards retractions for non-existent posts that are from someone other than the post's author" do
@ -154,7 +154,7 @@ describe "attack vectors" do
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform!
bob.visible_posts.count.should == 1
bob.visible_shareables(Post).count.should == 1
ret = Retraction.new
ret.post_guid = original_message.guid
@ -167,7 +167,7 @@ describe "attack vectors" do
zord.perform!
}.should raise_error /not a valid object/
bob.reload.visible_posts.count.should == 1
bob.reload.visible_shareables(Post).count.should == 1
end
it 'it should not allow you to send retractions for other people' do

View file

@ -61,7 +61,7 @@ describe 'a user receives a post' do
bob.dispatch_post(sm, :to => @bobs_aspect)
end
alice.visible_posts.count.should == 1
alice.visible_shareables(Post).count.should == 1
end
context 'with mentions, ' do
@ -153,14 +153,14 @@ describe 'a user receives a post' do
end
it "adds a received post to the the contact" do
alice.visible_posts.should include(@status_message)
alice.visible_shareables(Post).should include(@status_message)
@contact.posts.should include(@status_message)
end
it 'removes posts upon forceful removal' do
alice.remove_contact(@contact, :force => true)
alice.reload
alice.visible_posts.should_not include @status_message
alice.visible_shareables(Post).should_not include @status_message
end
context 'dependant delete' do
@ -240,7 +240,7 @@ describe 'a user receives a post' do
end
it 'should correctly attach the user already on the pod' do
bob.reload.visible_posts.size.should == 1
bob.reload.visible_shareables(Post).size.should == 1
post_in_db = StatusMessage.find(@post.id)
post_in_db.comments.should == []
receive_with_zord(bob, alice.person, @xml)
@ -264,7 +264,7 @@ describe 'a user receives a post' do
remote_person
}
bob.reload.visible_posts.size.should == 1
bob.reload.visible_shareables(Post).size.should == 1
post_in_db = StatusMessage.find(@post.id)
post_in_db.comments.should == []
@ -336,7 +336,7 @@ describe 'a user receives a post' do
zord = Postzord::Receiver::Private.new(bob, :salmon_xml => salmon_xml)
zord.perform!
bob.visible_posts.include?(post).should be_true
bob.visible_shareables(Post).include?(post).should be_true
end
end

View file

@ -95,11 +95,12 @@ describe RedisCache do
sql = "long_sql"
order = "created_at DESC"
@cache.should_receive(:order).and_return(order)
bob.should_receive(:visible_posts_sql).with(hash_including(
:type => RedisCache.acceptable_types,
:limit => RedisCache::CACHE_LIMIT,
:order => order)).
and_return(sql)
bob.should_receive(:visible_shareables_sql).with(Post,
hash_including(
:type => RedisCache.acceptable_types,
:limit => RedisCache::CACHE_LIMIT,
:order => order)).
and_return(sql)
Post.connection.should_receive(:select_all).with(sql).and_return([])

View file

@ -52,25 +52,25 @@ describe Stream::Aspect do
it 'calls visible posts for the given user' do
stream = Stream::Aspect.new(@alice, [1,2])
@alice.should_receive(:visible_posts).and_return(stub.as_null_object)
@alice.should_receive(:visible_shareables).and_return(stub.as_null_object)
stream.posts
end
it 'is called with 3 types' do
stream = Stream::Aspect.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 = AspectStream.new(@alice, [1,2], :order => 'created_at')
@alice.should_receive(:visible_shareables).with(Post, hash_including(:type=> ['StatusMessage', 'Reshare', 'ActivityStreams::Photo'])).and_return(stub.as_null_object)
stream.posts
end
it 'respects ordering' do
stream = Stream::Aspect.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 = AspectStream.new(@alice, [1,2], :order => 'created_at')
@alice.should_receive(:visible_shareables).with(Post, hash_including(:order => 'created_at DESC')).and_return(stub.as_null_object)
stream.posts
end
it 'respects max_time' do
stream = Stream::Aspect.new(@alice, [1,2], :max_time => 123)
@alice.should_receive(:visible_posts).with(hash_including(:max_time => instance_of(Time))).and_return(stub.as_null_object)
stream = AspectStream.new(@alice, [1,2], :max_time => 123)
@alice.should_receive(:visible_shareables).with(Post, hash_including(:max_time => instance_of(Time))).and_return(stub.as_null_object)
stream.posts
end
@ -78,7 +78,7 @@ describe Stream::Aspect do
stream = Stream::Aspect.new(@alice, [1,2], :max_time => 123)
all_aspects = mock
stream.stub(:for_all_aspects?).and_return(all_aspects)
@alice.should_receive(:visible_posts).with(hash_including(:all_aspects? => all_aspects)).and_return(stub.as_null_object)
@alice.should_receive(:visible_shareables).with(Post, hash_including(:all_aspects? => all_aspects)).and_return(stub.as_null_object)
stream.posts
end
end

View file

@ -46,7 +46,7 @@ describe 'making sure the spec runner works' do
it 'allows posting after running' do
message = @user1.post(:status_message, :text => "Connection!", :to => @aspect1.id)
@user2.reload.visible_posts.should include message
@user2.reload.visible_shareables(Post).should include message
end
end

View file

@ -221,7 +221,7 @@ describe Post do
describe "#receive" do
it 'returns false if the post does not verify' do
@post = Factory(:status_message, :author => bob.person)
@post.should_receive(:verify_persisted_post).and_return(false)
@post.should_receive(:verify_persisted_shareable).and_return(false)
@post.receive(bob, eve.person).should == false
end
end
@ -230,12 +230,12 @@ describe Post do
before do
@post = Factory.build(:status_message, :author => bob.person)
@known_post = Post.new
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_post => true))
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_shareable => true))
end
context "user knows about the post" do
before do
bob.stub(:find_visible_post_by_id).and_return(@known_post)
bob.stub(:find_visible_shareable_by_id).and_return(@known_post)
end
it 'updates attributes only if mutable' do
@ -253,7 +253,7 @@ describe Post do
context "the user does not know about the post" do
before do
bob.stub(:find_visible_post_by_id).and_return(nil)
bob.stub(:find_visible_shareable_by_id).and_return(nil)
bob.stub(:notify_if_mentioned).and_return(true)
end
@ -262,7 +262,7 @@ describe Post do
end
it 'notifies the user if they are mentioned' do
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_post => true))
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_shareable => true))
bob.should_receive(:notify_if_mentioned).and_return(true)
@post.send(:receive_persisted, bob, eve.person, @known_post).should == true
@ -274,17 +274,17 @@ describe Post do
context "the user does not know about the post" do
before do
@post = Factory.build(:status_message, :author => bob.person)
bob.stub(:find_visible_post_by_id).and_return(nil)
bob.stub(:find_visible_shareable_by_id).and_return(nil)
bob.stub(:notify_if_mentioned).and_return(true)
end
it "it receives the post from the contact of the author" do
bob.should_receive(:contact_for).with(eve.person).and_return(stub(:receive_post => true))
bob.should_receive(:contact_for).with(eve.person).and_return(stub(:receive_shareable => true))
@post.send(:receive_non_persisted, bob, eve.person).should == true
end
it 'notifies the user if they are mentioned' do
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_post => true))
bob.stub(:contact_for).with(eve.person).and_return(stub(:receive_shareable => true))
bob.should_receive(:notify_if_mentioned).and_return(true)
@post.send(:receive_non_persisted, bob, eve.person).should == true

View file

@ -22,8 +22,8 @@ describe User do
it 'saves post into visible post ids' do
lambda {
alice.add_to_streams(@post, @aspects)
}.should change{alice.visible_posts(:by_members_of => @aspects).length}.by(1)
alice.visible_posts(:by_members_of => @aspects).should include @post
}.should change{alice.visible_shareables(Post, :by_members_of => @aspects).length}.by(1)
alice.visible_shareables(Post, :by_members_of => @aspects).should include @post
end
it 'saves post into each aspect in aspect_ids' do

View file

@ -12,49 +12,50 @@ describe User do
@bobs_aspect = bob.aspects.where(:name => "generic").first
end
describe "#visible_post_ids" do
describe "#visible_shareable_ids" do
it "contains your public posts" do
public_post = alice.post(:status_message, :text => "hi", :to => @alices_aspect.id, :public => true)
alice.visible_post_ids.should include(public_post.id)
alice.visible_shareable_ids(Post).should include(public_post.id)
end
it "contains your non-public posts" do
private_post = alice.post(:status_message, :text => "hi", :to => @alices_aspect.id, :public => false)
alice.visible_post_ids.should include(private_post.id)
alice.visible_shareable_ids(Post).should include(private_post.id)
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_post_ids.should include(bobs_public_post.id)
alice.visible_shareable_ids(Post).should include(bobs_public_post.id)
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_post_ids.should include(bobs_post.id)
alice.visible_shareable_ids(Post).should include(bobs_post.id)
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_post_ids.should_not include(invisible_post.id)
alice.visible_shareable_ids(Post).should_not include(invisible_post.id)
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_post_ids.should_not include pending_post.id
alice.visible_shareable_ids(Post).should_not include pending_post.id
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_post_ids.should_not include pending_photo.id
alice.visible_shareable_ids(Photo).should_not include pending_photo.id
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_post_ids(:type => "Photo").should include(photo.id)
alice.visible_post_ids(:type => 'StatusMessage').should_not include(photo.id)
post = bob.post(:status_message, :text => "hey", :public => true, :to => @bobs_aspect.id, :pending => false)
reshare = bob.post(:reshare, :pending => false, :root_guid => post.guid, :to => @bobs_aspect)
alice.visible_shareable_ids(Post, :type => "Reshare").should include(reshare.id)
alice.visible_shareable_ids(Post, :type => 'StatusMessage').should_not include(reshare.id)
end
it "does not contain duplicate posts" do
@ -64,24 +65,24 @@ describe User do
bobs_post = bob.post(:status_message, :text => "hai to all my people", :to => [@bobs_aspect.id, bobs_other_aspect.id])
alice.visible_post_ids.length.should == 1
alice.visible_post_ids.should include(bobs_post.id)
alice.visible_shareable_ids(Post).length.should == 1
alice.visible_shareable_ids(Post).should include(bobs_post.id)
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.share_visibilities.first
@vis = @status.share_visibilities(Post).first
end
it "pulls back non hidden posts" do
alice.visible_post_ids.include?(@status.id).should be_true
alice.visible_shareable_ids(Post).include?(@status.id).should be_true
end
it "does not pull back hidden posts" do
@vis.update_attributes(:hidden => true)
alice.visible_post_ids.include?(@status.id).should be_false
alice.visible_shareable_ids(Post).include?(@status.id).should be_false
end
end
@ -98,16 +99,16 @@ describe User do
it "gets populated with latest 100 posts" do
cache = mock(:cache_exists? => true, :supported_order? => true, :ensure_populated! => mock, :post_ids => [])
RedisCache.stub(:new).and_return(cache)
@opts = alice.send(:prep_opts, @opts)
@opts = alice.send(:prep_opts, Post, @opts)
cache.should_receive(:ensure_populated!).with(hash_including(@opts))
alice.visible_post_ids(@opts)
alice.visible_shareable_ids(Post, @opts)
end
it 'does not get used if if all_aspects? option is not present' do
RedisCache.should_not_receive(:new)
alice.visible_post_ids(@opts.merge({:all_aspects? => false}))
alice.visible_shareable_ids(Post, @opts.merge({:all_aspects? => false}))
end
describe "#ensure_populated_cache" do
@ -124,14 +125,14 @@ describe User do
it "reads from the cache" do
@cache.should_receive(:post_ids).and_return([1,2,3])
alice.visible_post_ids(@opts.merge({:limit => 3})).should == [1,2,3]
alice.visible_shareable_ids(Post, @opts.merge({:limit => 3})).should == [1,2,3]
end
it "queries if maxtime is later than the last cached post" do
@cache.stub(:post_ids).and_return([])
alice.should_receive(:visible_ids_from_sql)
alice.visible_post_ids(@opts)
alice.visible_shareable_ids(Post, @opts)
end
it "does not get repopulated" do
@ -144,8 +145,8 @@ describe User do
it "defaults the opts" do
time = Time.now
Time.stub(:now).and_return(time)
alice.send(:prep_opts, {}).should == {
:type => Stream::Base::TYPES_OF_POST_IN_STREAM,
alice.send(:prep_opts, Post, {}).should == {
:type => BaseStream::TYPES_OF_POST_IN_STREAM,
:order => 'created_at DESC',
:limit => 15,
:hidden => false,
@ -156,7 +157,7 @@ describe User do
end
end
describe "#visible_posts" do
describe "#visible_shareables" do
context 'with many posts' do
before do
bob.move_contact(eve.person, @bobs_aspect, bob.aspects.create(:name => 'new aspect'))
@ -175,53 +176,53 @@ describe User do
end
it 'works' do # The set up takes a looong time, so to save time we do several tests in one
bob.visible_posts.length.should == 15 #it returns 15 by default
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_shareables(Post).length.should == 15 #it returns 15 by default
bob.visible_shareables(Post).should == bob.visible_shareables(Post, :by_members_of => bob.aspects.map { |a| a.id }) # it is the same when joining through aspects
# checks the default sort order
bob.visible_posts.sort_by { |p| p.created_at }.map { |p| p.id }.should == bob.visible_posts.map { |p| p.id }.reverse #it is sorted updated_at desc by default
bob.visible_shareables(Post).sort_by { |p| p.created_at }.map { |p| p.id }.should == bob.visible_shareables(Post).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
bob.visible_shareables(Post, opts).first.created_at.should > bob.visible_shareables(Post, 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
bob.visible_shareables(Post, opts).first.updated_at.should > bob.visible_shareables(Post, opts).last.updated_at
# It should respect the limit option
opts = {:limit => 40}
bob.visible_posts(opts).length.should == 40
bob.visible_posts(opts).should == bob.visible_posts(opts.merge(:by_members_of => bob.aspects.map { |a| a.id }))
bob.visible_posts(opts).sort_by { |p| p.created_at }.map { |p| p.id }.should == bob.visible_posts(opts).map { |p| p.id }.reverse
bob.visible_shareables(Post, opts).length.should == 40
bob.visible_shareables(Post, opts).should == bob.visible_shareables(Post, opts.merge(:by_members_of => bob.aspects.map { |a| a.id }))
bob.visible_shareables(Post, opts).sort_by { |p| p.created_at }.map { |p| p.id }.should == bob.visible_shareables(Post, opts).map { |p| p.id }.reverse
# It should paginate using a datetime timestamp
last_time_of_last_page = bob.visible_posts.last.created_at
last_time_of_last_page = bob.visible_shareables(Post).last.created_at
opts = {:max_time => last_time_of_last_page}
bob.visible_posts(opts).length.should == 15
bob.visible_posts(opts).map { |p| p.id }.should == bob.visible_posts(opts.merge(:by_members_of => bob.aspects.map { |a| a.id })).map { |p| p.id }
bob.visible_posts(opts).sort_by { |p| p.created_at}.map { |p| p.id }.should == bob.visible_posts(opts).map { |p| p.id }.reverse
bob.visible_posts(opts).map { |p| p.id }.should == bob.visible_posts(:limit => 40)[15...30].map { |p| p.id } #pagination should return the right posts
bob.visible_shareables(Post, opts).length.should == 15
bob.visible_shareables(Post, opts).map { |p| p.id }.should == bob.visible_shareables(Post, opts.merge(:by_members_of => bob.aspects.map { |a| a.id })).map { |p| p.id }
bob.visible_shareables(Post, opts).sort_by { |p| p.created_at}.map { |p| p.id }.should == bob.visible_shareables(Post, opts).map { |p| p.id }.reverse
bob.visible_shareables(Post, opts).map { |p| p.id }.should == bob.visible_shareables(Post, :limit => 40)[15...30].map { |p| p.id } #pagination should return the right posts
# It should paginate using an integer timestamp
opts = {:max_time => last_time_of_last_page.to_i}
bob.visible_posts(opts).length.should == 15
bob.visible_posts(opts).map { |p| p.id }.should == bob.visible_posts(opts.merge(:by_members_of => bob.aspects.map { |a| a.id })).map { |p| p.id }
bob.visible_posts(opts).sort_by { |p| p.created_at}.map { |p| p.id }.should == bob.visible_posts(opts).map { |p| p.id }.reverse
bob.visible_posts(opts).map { |p| p.id }.should == bob.visible_posts(:limit => 40)[15...30].map { |p| p.id } #pagination should return the right posts
bob.visible_shareables(Post, opts).length.should == 15
bob.visible_shareables(Post, opts).map { |p| p.id }.should == bob.visible_shareables(Post, opts.merge(:by_members_of => bob.aspects.map { |a| a.id })).map { |p| p.id }
bob.visible_shareables(Post, opts).sort_by { |p| p.created_at}.map { |p| p.id }.should == bob.visible_shareables(Post, opts).map { |p| p.id }.reverse
bob.visible_shareables(Post, opts).map { |p| p.id }.should == bob.visible_shareables(Post, :limit => 40)[15...30].map { |p| p.id } #pagination should return the right posts
end
end
end
describe '#find_visible_post_by_id' do
describe '#find_visible_shareable_by_id' do
it "returns a post if you can see it" do
bobs_post = bob.post(:status_message, :text => "hi", :to => @bobs_aspect.id, :public => false)
alice.find_visible_post_by_id(bobs_post.id).should == bobs_post
alice.find_visible_shareable_by_id(Post, bobs_post.id).should == bobs_post
end
it "returns nil if you can't see that post" do
dogs = bob.aspects.create(:name => "dogs")
invisible_post = bob.post(:status_message, :text => "foobar", :to => dogs.id)
alice.find_visible_post_by_id(invisible_post.id).should be_nil
alice.find_visible_shareable_by_id(Post, invisible_post.id).should be_nil
end
end