From 84cfbd22fc49c9f8d96f4f78f9dd455be2a658c0 Mon Sep 17 00:00:00 2001
From: Dennis Schubert <mail@dennis-schubert.de>
Date: Fri, 4 Mar 2016 13:33:32 +0100
Subject: [PATCH] Back out #6723 due to Postgres breakage

This reverts commit 832a56134b7a1d5dc043730299c85685c9d00e86, reversing
changes made to 75c3e6068ca1ed19ab9eb8710927cc9159c2bedc.
---
 Changelog.md                                  |   1 -
 app/models/contact.rb                         |   7 +
 app/models/share_visibility.rb                |  28 ++--
 app/models/user.rb                            |   6 -
 app/models/user/connecting.rb                 |  14 ++
 app/models/user/querying.rb                   | 158 +++++++++---------
 ...32049_link_share_visibilities_with_user.rb |  65 -------
 db/schema.rb                                  |  94 ++++++-----
 lib/account_deleter.rb                        |   9 +-
 lib/diaspora/federated/shareable.rb           |  24 +--
 lib/diaspora/shareable.rb                     |  66 +++-----
 lib/evil_query.rb                             |  56 ++++++-
 lib/postzord/receiver/local_batch.rb          |   3 +-
 lib/stream/tag.rb                             |  11 +-
 spec/integration/account_deletion_spec.rb     | 116 ++++++-------
 spec/integration/receiving_spec.rb            |  34 +++-
 spec/lib/account_deleter_spec.rb              |  19 ++-
 .../lib/postzord/receiver/local_batch_spec.rb |   2 +-
 .../remove_duplicate_and_empty_pods_spec.rb   |  64 +++++++
 spec/models/post_spec.rb                      |  44 ++---
 spec/models/share_visibility_spec.rb          |  52 +++---
 spec/models/user/connecting_spec.rb           |  15 ++
 spec/models/user/querying_spec.rb             |  18 +-
 spec/shared_behaviors/account_deletion.rb     |   4 +
 24 files changed, 521 insertions(+), 389 deletions(-)
 delete mode 100644 db/migrate/20160225232049_link_share_visibilities_with_user.rb
 create mode 100644 spec/migrations/remove_duplicate_and_empty_pods_spec.rb

diff --git a/Changelog.md b/Changelog.md
index 62269bca23..2dfddc7f2d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -89,7 +89,6 @@ Contributions are very welcome, the hard work is done!
 * Stream faces are gone [#6686](https://github.com/diaspora/diaspora/pull/6686)
 * Refactor mobile javascript and add tests [#6394](https://github.com/diaspora/diaspora/pull/6394)
 * Dropped `parent_author_signature` from relayables [#6586](https://github.com/diaspora/diaspora/pull/6586)
-* Attached ShareVisibilities to the User, not the Contact [#6723](https://github.com/diaspora/diaspora/pull/6723)
 
 ## Bug fixes
 * Destroy Participation when removing interactions with a post [#5852](https://github.com/diaspora/diaspora/pull/5852)
diff --git a/app/models/contact.rb b/app/models/contact.rb
index a2ffedae4b..861ec07715 100644
--- a/app/models/contact.rb
+++ b/app/models/contact.rb
@@ -14,6 +14,9 @@ class Contact < ActiveRecord::Base
   has_many :aspect_memberships, :dependent => :destroy
   has_many :aspects, :through => :aspect_memberships
 
+  has_many :share_visibilities, :source => :shareable, :source_type => 'Post'
+  has_many :posts, :through => :share_visibilities, :source => :shareable, :source_type => 'Post'
+
   validate :not_contact_for_self,
            :not_blocked_user,
            :not_contact_with_closed_account
@@ -57,6 +60,10 @@ class Contact < ActiveRecord::Base
                 :into => aspects.first)
   end
 
+  def receive_shareable(shareable)
+    ShareVisibility.create!(:shareable_id => shareable.id, :shareable_type => shareable.class.base_class.to_s, :contact_id => self.id)
+  end
+
   def contacts
     people = Person.arel_table
     incoming_aspects = Aspect.where(
diff --git a/app/models/share_visibility.rb b/app/models/share_visibility.rb
index 0eda3b0d84..060f65cd2f 100644
--- a/app/models/share_visibility.rb
+++ b/app/models/share_visibility.rb
@@ -3,36 +3,40 @@
 #   the COPYRIGHT file.
 
 class ShareVisibility < ActiveRecord::Base
-  belongs_to :user
+  belongs_to :contact
   belongs_to :shareable, :polymorphic => :true
 
-  scope :for_a_user, ->(user) {
-    where(user_id: user.id)
+  scope :for_a_users_contacts, ->(user) {
+    where(:contact_id => user.contacts.map {|c| c.id})
+  }
+
+  scope :for_contacts_of_a_person, ->(person) {
+    where(:contact_id => person.contacts.map {|c| c.id})
   }
 
   validate :not_public
 
-  # Perform a batch import, given a set of users and a shareable
+  # Perform a batch import, given a set of contacts and a shareable
   # @note performs a bulk insert in mySQL; performs linear insertions in postgres
-  # @param user_ids [Array<Integer>] Recipients
+  # @param contacts [Array<Contact>] Recipients
   # @param share [Shareable]
   # @return [void]
-  def self.batch_import(user_ids, share)
+  def self.batch_import(contact_ids, share)
     return false unless ShareVisibility.new(:shareable_id => share.id, :shareable_type => share.class.to_s).valid?
 
     if AppConfig.postgres?
-      user_ids.each do |user_id|
+      contact_ids.each do |contact_id|
         ShareVisibility.find_or_create_by(
-          user_id:        user_id,
-          shareable_id:   share.id,
+          contact_id: contact_id,
+          shareable_id: share.id,
           shareable_type: share.class.base_class.to_s
         )
       end
     else
-      new_share_visibilities_data = user_ids.map do |user_id|
-        [user_id, share.id, share.class.base_class.to_s]
+       new_share_visibilities_data = contact_ids.map do |contact_id|
+        [contact_id, share.id, share.class.base_class.to_s]
       end
-      ShareVisibility.import(%i(user_id shareable_id shareable_type), new_share_visibilities_data)
+      ShareVisibility.import([:contact_id, :shareable_id, :shareable_type], new_share_visibilities_data)
     end
   end
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 598ec8cb64..ec009aa473 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -80,8 +80,6 @@ class User < ActiveRecord::Base
   has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
   has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"
 
-  has_many :share_visibilities
-
   before_save :guard_unconfirmed_email,
               :save_person!
 
@@ -116,10 +114,6 @@ class User < ActiveRecord::Base
     InvitationCode.find_or_create_by(user_id: self.id)
   end
 
-  def receive_shareable(shareable)
-    ShareVisibility.create!(shareable_id: shareable.id, shareable_type: shareable.class.base_class.to_s, user_id: id)
-  end
-
   def hidden_shareables
     self[:hidden_shareables] ||= {}
   end
diff --git a/app/models/user/connecting.rb b/app/models/user/connecting.rb
index a1f425206b..333e6bd5ed 100644
--- a/app/models/user/connecting.rb
+++ b/app/models/user/connecting.rb
@@ -24,9 +24,23 @@ module User::Connecting
     end
 
     deliver_profile_update
+    register_share_visibilities(contact)
     contact
   end
 
+  # This puts the last 100 public posts by the passed in contact into the user's stream.
+  # @param [Contact] contact
+  # @return [void]
+  def register_share_visibilities(contact)
+    #should have select here, but proven hard to test
+    posts = Post.where(:author_id => contact.person_id, :public => true).limit(100)
+    p = posts.map do |post|
+      ShareVisibility.new(:contact_id => contact.id, :shareable_id => post.id, :shareable_type => 'Post')
+    end
+    ShareVisibility.import(p) unless posts.empty?
+    nil
+  end
+
   def remove_contact(contact, opts={:force => false, :retracted => false})
     if !contact.mutual? || opts[:force]
       contact.destroy
diff --git a/app/models/user/querying.rb b/app/models/user/querying.rb
index 531c8f5fa4..20d52df454 100644
--- a/app/models/user/querying.rb
+++ b/app/models/user/querying.rb
@@ -13,12 +13,90 @@ module User::Querying
   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.table_name}.*")
-      .limit(opts[:limit]).order(opts[:order_with_table])
+    klass.where(:id => shareable_ids).select('DISTINCT '+klass.to_s.tableize+'.*').limit(opts[:limit]).order(opts[:order_with_table]).order(klass.table_name+".id DESC")
   end
 
   def visible_shareable_ids(klass, opts={})
-    visible_ids_from_sql(klass, prep_opts(klass, opts))
+    opts = prep_opts(klass, opts)
+    visible_ids_from_sql(klass, opts)
+  end
+
+  # @return [Array<Integer>]
+  def visible_ids_from_sql(klass, opts={})
+    opts = prep_opts(klass, opts)
+    opts[:klass] = klass
+    opts[:by_members_of] ||= self.aspect_ids
+
+    post_ids = klass.connection.select_values(visible_shareable_sql(klass, opts)).map(&:to_i)
+    post_ids += klass.connection.select_values("#{construct_public_followings_sql(opts).to_sql} LIMIT #{opts[:limit]}").map {|id| id.to_i }
+  end
+
+  def visible_shareable_sql(klass, opts={})
+    table = klass.table_name
+    opts = prep_opts(klass, opts)
+    opts[:klass] = klass
+
+    shareable_from_others = construct_shareable_from_others_query(opts)
+    shareable_from_self = construct_shareable_from_self_query(opts)
+
+    "(#{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 ugly_select_clause(query, opts)
+    klass = opts[:klass]
+    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]
+    query.select(select_clause).order(opts[:order_with_table]).where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
+  end
+
+  def construct_shareable_from_others_query(opts)
+    conditions = {
+        :pending => false,
+        :share_visibilities => {:hidden => opts[:hidden]},
+        :contacts => {:user_id => self.id, :receiving => true}
+    }
+
+    conditions[:type] = opts[:type] if opts.has_key?(:type)
+
+    query = opts[:klass].joins(:contacts).where(conditions)
+
+    if opts[:by_members_of]
+      query = query.joins(:contacts => :aspect_memberships).where(
+        :aspect_memberships => {:aspect_id => opts[:by_members_of]})
+    end
+
+    ugly_select_clause(query, opts)
+  end
+
+  def construct_public_followings_sql(opts)
+    logger.debug "[EVIL-QUERY] user.construct_public_followings_sql"
+
+    # For PostgreSQL and MySQL/MariaDB we use a different query
+    # see issue: https://github.com/diaspora/diaspora/issues/5014
+    if AppConfig.postgres?
+      query = opts[:klass].where(:author_id => Person.in_aspects(opts[:by_members_of]).select("people.id"), :public => true, :pending => false)
+    else
+      aspects = Aspect.where(:id => opts[:by_members_of])
+      person_ids = Person.connection.select_values(people_in_aspects(aspects).select("people.id").to_sql)
+      query = opts[:klass].where(:author_id => person_ids, :public => true, :pending => false)
+    end
+
+    unless(opts[:klass] == Photo)
+      query = query.where(:type => opts[:type])
+    end
+
+    ugly_select_clause(query, opts)
+  end
+
+  def construct_shareable_from_self_query(opts)
+    conditions = {:pending => false, :author_id => self.person_id }
+    conditions[:type] = opts[:type] if opts.has_key?(:type)
+    query = opts[:klass].where(conditions)
+
+    if opts[:by_members_of]
+      query = query.joins(:aspect_visibilities).where(:aspect_visibilities => {:aspect_id => opts[:by_members_of]})
+    end
+
+    ugly_select_clause(query, opts)
   end
 
   def contact_for(person)
@@ -66,88 +144,18 @@ module User::Querying
   end
 
   def posts_from(person)
-    Post.from_person_visible_by_user(self, person).order("posts.created_at desc")
+    ::EvilQuery::ShareablesFromPerson.new(self, Post, person).make_relation!
   end
 
   def photos_from(person, opts={})
     opts = prep_opts(Photo, opts)
-    Photo.from_person_visible_by_user(self, person)
+    ::EvilQuery::ShareablesFromPerson.new(self, Photo, person).make_relation!
       .by_max_time(opts[:max_time])
       .limit(opts[:limit])
   end
 
   protected
 
-  # @return [Array<Integer>]
-  def visible_ids_from_sql(klass, opts)
-    opts[:klass] = klass
-    opts[:by_members_of] ||= aspect_ids
-
-    klass.connection.select_values(visible_shareable_sql(opts)).map(&:to_i)
-  end
-
-  def visible_shareable_sql(opts)
-    shareable_from_others = construct_shareable_from_others_query(opts)
-    shareable_from_self = construct_shareable_from_self_query(opts)
-
-    "(#{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 construct_shareable_from_others_query(opts)
-    logger.debug "[EVIL-QUERY] user.construct_shareable_from_others_query"
-
-    query = visible_shareables_query(posts_from_aspects_query(opts), opts)
-
-    query = query.where(type: opts[:type]) unless opts[:klass] == Photo
-
-    ugly_select_clause(query, opts)
-  end
-
-  # For PostgreSQL and MySQL/MariaDB we use a different query
-  # see issue: https://github.com/diaspora/diaspora/issues/5014
-  def posts_from_aspects_query(opts)
-    if AppConfig.postgres?
-      opts[:klass].where(author_id: Person.in_aspects(opts[:by_members_of]).select("people.id"))
-    else
-      person_ids = Person.connection.select_values(Person.in_aspects(opts[:by_members_of]).select("people.id").to_sql)
-      opts[:klass].where(author_id: person_ids)
-    end
-  end
-
-  def visible_shareables_query(query, opts)
-    query.with_visibility.where(pending: false).where(
-      visible_private_shareables(opts).or(opts[:klass].arel_table[:public].eq(true))
-    )
-  end
-
-  def visible_private_shareables(opts)
-    ShareVisibility.arel_table[:user_id].eq(id)
-      .and(ShareVisibility.arel_table[:shareable_type].eq(opts[:klass].to_s))
-      .and(ShareVisibility.arel_table[:hidden].eq(opts[:hidden]))
-  end
-
-  def construct_shareable_from_self_query(opts)
-    conditions = {pending: false, author_id: person_id}
-    conditions[:type] = opts[:type] if opts.has_key?(:type)
-    query = opts[:klass].where(conditions)
-
-    unless opts[:all_aspects?]
-      query = query.joins(:aspect_visibilities).where(aspect_visibilities: {aspect_id: opts[:by_members_of]})
-    end
-
-    ugly_select_clause(query, opts)
-  end
-
-  def ugly_select_clause(query, opts)
-    klass = opts[:klass]
-    table = klass.table_name
-    select_clause = "DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at" % [table, table, table]
-    query.select(select_clause).order(opts[:order_with_table])
-      .where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
-  end
-
   # @return [Hash]
   def prep_opts(klass, opts)
     defaults = {
diff --git a/db/migrate/20160225232049_link_share_visibilities_with_user.rb b/db/migrate/20160225232049_link_share_visibilities_with_user.rb
deleted file mode 100644
index 1528c9aed4..0000000000
--- a/db/migrate/20160225232049_link_share_visibilities_with_user.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-class LinkShareVisibilitiesWithUser < ActiveRecord::Migration
-  def up
-    cleanup_deleted_share_visibilities
-
-    remove_columns :share_visibilities, :created_at, :updated_at
-    add_column :share_visibilities, :user_id, :integer
-
-    ShareVisibility.joins("INNER JOIN contacts ON share_visibilities.contact_id = contacts.id")
-      .update_all("share_visibilities.user_id = contacts.user_id")
-
-    remove_foreign_key :share_visibilities, name: :post_visibilities_contact_id_fk
-
-    remove_index :share_visibilities, name: :index_post_visibilities_on_contact_id
-    remove_index :share_visibilities, name: :shareable_and_contact_id
-    remove_index :share_visibilities, name: :shareable_and_hidden_and_contact_id
-
-    remove_column :share_visibilities, :contact_id
-    change_column :share_visibilities, :user_id, :integer, null: false
-
-    ShareVisibility.joins("LEFT OUTER JOIN users ON users.id = share_visibilities.user_id")
-      .delete_all("users.id is NULL")
-
-    add_index :share_visibilities, :user_id
-    add_index :share_visibilities, %i(shareable_id shareable_type user_id), name: :shareable_and_user_id
-    add_index :share_visibilities, %i(shareable_id shareable_type hidden user_id),
-              name: :shareable_and_hidden_and_user_id
-
-    add_foreign_key :share_visibilities, :users, name: :share_visibilities_user_id_fk, on_delete: :cascade
-  end
-
-  def down
-    add_column :share_visibilities, :contact_id, :integer
-
-    ShareVisibility.joins("INNER JOIN contacts ON share_visibilities.user_id = contacts.user_id")
-      .update_all("share_visibilities.contact_id = contacts.id")
-
-    remove_foreign_key :share_visibilities, name: :share_visibilities_user_id_fk
-
-    remove_index :share_visibilities, :user_id
-    remove_index :share_visibilities, name: :shareable_and_user_id
-    remove_index :share_visibilities, name: :shareable_and_hidden_and_user_id
-
-    remove_column :share_visibilities, :user_id
-    change_column :share_visibilities, :contact_id, :integer, null: false
-
-    add_index :share_visibilities, :contact_id, name: :index_post_visibilities_on_contact_id
-    add_index :share_visibilities, %i(shareable_id shareable_type contact_id), name: :shareable_and_contact_id
-    add_index :share_visibilities, %i(shareable_id shareable_type hidden contact_id),
-              name: :shareable_and_hidden_and_contact_id
-
-    add_foreign_key :share_visibilities, :contacts, name: :post_visibilities_contact_id_fk, on_delete: :cascade
-
-    add_column :share_visibilities, :created_at, :datetime
-    add_column :share_visibilities, :updated_at, :datetime
-  end
-
-  private
-
-  def cleanup_deleted_share_visibilities
-    ShareVisibility.joins("LEFT OUTER JOIN posts ON posts.id = share_visibilities.shareable_id")
-      .where(shareable_type: "Post").delete_all("posts.id is NULL")
-    ShareVisibility.joins("LEFT OUTER JOIN photos ON photos.id = share_visibilities.shareable_id")
-      .where(shareable_type: "Photo").delete_all("photos.id is NULL")
-  end
-end
diff --git a/db/schema.rb b/db/schema.rb
index 04230193d3..5214e27e44 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160225232049) do
+ActiveRecord::Schema.define(version: 20151210213023) do
 
   create_table "account_deletions", force: :cascade do |t|
     t.string   "diaspora_handle", limit: 255
@@ -103,15 +103,15 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   end
 
   create_table "comments", force: :cascade do |t|
-    t.text     "text",             limit: 65535,                  null: false
-    t.integer  "commentable_id",   limit: 4,                      null: false
-    t.integer  "author_id",        limit: 4,                      null: false
-    t.string   "guid",             limit: 255,                    null: false
-    t.text     "author_signature", limit: 65535
-    t.datetime "created_at",                                      null: false
-    t.datetime "updated_at",                                      null: false
-    t.integer  "likes_count",      limit: 4,     default: 0,      null: false
-    t.string   "commentable_type", limit: 60,    default: "Post", null: false
+    t.text     "text",                    limit: 65535,                  null: false
+    t.integer  "commentable_id",          limit: 4,                      null: false
+    t.integer  "author_id",               limit: 4,                      null: false
+    t.string   "guid",                    limit: 255,                    null: false
+    t.text     "author_signature",        limit: 65535
+    t.datetime "created_at",                                             null: false
+    t.datetime "updated_at",                                             null: false
+    t.integer  "likes_count",             limit: 4,     default: 0,      null: false
+    t.string   "commentable_type",        limit: 60,    default: "Post", null: false
   end
 
   add_index "comments", ["author_id"], name: "index_comments_on_person_id", using: :btree
@@ -188,14 +188,14 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   add_index "invitations", ["sender_id"], name: "index_invitations_on_sender_id", using: :btree
 
   create_table "likes", force: :cascade do |t|
-    t.boolean  "positive",                       default: true
-    t.integer  "target_id",        limit: 4
-    t.integer  "author_id",        limit: 4
-    t.string   "guid",             limit: 255
-    t.text     "author_signature", limit: 65535
-    t.datetime "created_at",                                    null: false
-    t.datetime "updated_at",                                    null: false
-    t.string   "target_type",      limit: 60,                   null: false
+    t.boolean  "positive",                              default: true
+    t.integer  "target_id",               limit: 4
+    t.integer  "author_id",               limit: 4
+    t.string   "guid",                    limit: 255
+    t.text     "author_signature",        limit: 65535
+    t.datetime "created_at",                                           null: false
+    t.datetime "updated_at",                                           null: false
+    t.string   "target_type",             limit: 60,                   null: false
   end
 
   add_index "likes", ["author_id"], name: "likes_author_id_fk", using: :btree
@@ -222,13 +222,13 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   add_index "mentions", ["post_id"], name: "index_mentions_on_post_id", using: :btree
 
   create_table "messages", force: :cascade do |t|
-    t.integer  "conversation_id",  limit: 4,     null: false
-    t.integer  "author_id",        limit: 4,     null: false
-    t.string   "guid",             limit: 255,   null: false
-    t.text     "text",             limit: 65535, null: false
-    t.datetime "created_at",                     null: false
-    t.datetime "updated_at",                     null: false
-    t.text     "author_signature", limit: 65535
+    t.integer  "conversation_id",         limit: 4,     null: false
+    t.integer  "author_id",               limit: 4,     null: false
+    t.string   "guid",                    limit: 255,   null: false
+    t.text     "text",                    limit: 65535, null: false
+    t.datetime "created_at",                            null: false
+    t.datetime "updated_at",                            null: false
+    t.text     "author_signature",        limit: 65535
   end
 
   add_index "messages", ["author_id"], name: "index_messages_on_author_id", using: :btree
@@ -312,14 +312,14 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   end
 
   create_table "participations", force: :cascade do |t|
-    t.string   "guid",             limit: 255
-    t.integer  "target_id",        limit: 4
-    t.string   "target_type",      limit: 60,                null: false
-    t.integer  "author_id",        limit: 4
-    t.text     "author_signature", limit: 65535
-    t.datetime "created_at",                                 null: false
-    t.datetime "updated_at",                                 null: false
-    t.integer  "count",            limit: 4,     default: 1, null: false
+    t.string   "guid",                    limit: 255
+    t.integer  "target_id",               limit: 4
+    t.string   "target_type",             limit: 60,                null: false
+    t.integer  "author_id",               limit: 4
+    t.text     "author_signature",        limit: 65535
+    t.datetime "created_at",                                        null: false
+    t.datetime "updated_at",                                        null: false
+    t.integer  "count",                   limit: 4,     default: 1, null: false
   end
 
   add_index "participations", ["guid"], name: "index_participations_on_guid", length: {"guid"=>191}, using: :btree
@@ -392,11 +392,11 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   add_index "poll_answers", ["poll_id"], name: "index_poll_answers_on_poll_id", using: :btree
 
   create_table "poll_participations", force: :cascade do |t|
-    t.integer  "poll_answer_id",   limit: 4,     null: false
-    t.integer  "author_id",        limit: 4,     null: false
-    t.integer  "poll_id",          limit: 4,     null: false
-    t.string   "guid",             limit: 255
-    t.text     "author_signature", limit: 65535
+    t.integer  "poll_answer_id",          limit: 4,     null: false
+    t.integer  "author_id",               limit: 4,     null: false
+    t.integer  "poll_id",                 limit: 4,     null: false
+    t.string   "guid",                    limit: 255
+    t.text     "author_signature",        limit: 65535
     t.datetime "created_at"
     t.datetime "updated_at"
   end
@@ -542,16 +542,18 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   add_index "services", ["user_id"], name: "index_services_on_user_id", using: :btree
 
   create_table "share_visibilities", force: :cascade do |t|
-    t.integer "shareable_id",   limit: 4,                   null: false
-    t.boolean "hidden",                    default: false,  null: false
-    t.string  "shareable_type", limit: 60, default: "Post", null: false
-    t.integer "user_id",        limit: 4,                   null: false
+    t.integer  "shareable_id",   limit: 4,                   null: false
+    t.datetime "created_at",                                 null: false
+    t.datetime "updated_at",                                 null: false
+    t.boolean  "hidden",                    default: false,  null: false
+    t.integer  "contact_id",     limit: 4,                   null: false
+    t.string   "shareable_type", limit: 60, default: "Post", null: false
   end
 
-  add_index "share_visibilities", ["shareable_id", "shareable_type", "hidden", "user_id"], name: "shareable_and_hidden_and_user_id", using: :btree
-  add_index "share_visibilities", ["shareable_id", "shareable_type", "user_id"], name: "shareable_and_user_id", using: :btree
+  add_index "share_visibilities", ["contact_id"], name: "index_post_visibilities_on_contact_id", using: :btree
+  add_index "share_visibilities", ["shareable_id", "shareable_type", "contact_id"], name: "shareable_and_contact_id", using: :btree
+  add_index "share_visibilities", ["shareable_id", "shareable_type", "hidden", "contact_id"], name: "shareable_and_hidden_and_contact_id", using: :btree
   add_index "share_visibilities", ["shareable_id"], name: "index_post_visibilities_on_post_id", using: :btree
-  add_index "share_visibilities", ["user_id"], name: "index_share_visibilities_on_user_id", using: :btree
 
   create_table "simple_captcha_data", force: :cascade do |t|
     t.string   "key",        limit: 40
@@ -677,5 +679,5 @@ ActiveRecord::Schema.define(version: 20160225232049) do
   add_foreign_key "ppid", "users"
   add_foreign_key "profiles", "people", name: "profiles_person_id_fk", on_delete: :cascade
   add_foreign_key "services", "users", name: "services_user_id_fk", on_delete: :cascade
-  add_foreign_key "share_visibilities", "users", name: "share_visibilities_user_id_fk", on_delete: :cascade
+  add_foreign_key "share_visibilities", "contacts", name: "post_visibilities_contact_id_fk", on_delete: :cascade
 end
diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb
index 7e8fee278f..4d03351008 100644
--- a/lib/account_deleter.rb
+++ b/lib/account_deleter.rb
@@ -27,6 +27,7 @@ class AccountDeleter
       #person
       delete_standard_person_associations
       remove_conversation_visibilities
+      remove_share_visibilities_on_persons_posts
       delete_contacts_of_me
       tombstone_person_and_profile
 
@@ -55,7 +56,7 @@ class AccountDeleter
 
   def ignored_ar_user_associations
     %i(followed_tags invited_by contact_people aspect_memberships
-       ignored_people share_visibilities conversation_visibilities conversations reports)
+       ignored_people conversation_visibilities conversations reports)
   end
 
   def delete_standard_user_associations
@@ -82,8 +83,12 @@ class AccountDeleter
 
   # Currently this would get deleted due to the db foreign key constrainsts,
   # but we'll keep this method here for completeness
+  def remove_share_visibilities_on_persons_posts
+    ShareVisibility.for_contacts_of_a_person(person).destroy_all
+  end
+
   def remove_share_visibilities_on_contacts_posts
-    ShareVisibility.for_a_user(user).destroy_all
+    ShareVisibility.for_a_users_contacts(user).destroy_all
   end
 
   def remove_conversation_visibilities
diff --git a/lib/diaspora/federated/shareable.rb b/lib/diaspora/federated/shareable.rb
index b2ffee6b92..0b29da8d8c 100644
--- a/lib/diaspora/federated/shareable.rb
+++ b/lib/diaspora/federated/shareable.rb
@@ -33,14 +33,14 @@ module Diaspora
         end
 
         # @param [User] user The user that is receiving this shareable.
-        # @param [Person] _person The sender of the shareable
+        # @param [Person] person The person who dispatched this shareable to the
         # @return [void]
-        def receive(user, _person)
+        def receive(user, person)
           local_shareable = persisted_shareable
           if local_shareable
-            receive_persisted(user, local_shareable) if verify_persisted_shareable(local_shareable)
+            receive_persisted(user, person, local_shareable) if verify_persisted_shareable(local_shareable)
           else
-            receive_non_persisted(user)
+            receive_non_persisted(user, person)
           end
         end
 
@@ -81,12 +81,12 @@ module Diaspora
           false
         end
 
-        def receive_persisted(user, shareable)
+        def receive_persisted(user, person, shareable)
           known_shareable = user.find_visible_shareable_by_id(self.class.base_class, guid, key: :guid)
           if known_shareable
             update_existing_sharable(known_shareable)
           else
-            receive_shareable_visibility(user, shareable)
+            receive_shareable_visibility(user, person, shareable)
           end
         end
 
@@ -101,18 +101,18 @@ module Diaspora
           end
         end
 
-        def receive_shareable_visibility(user, shareable)
-          user.receive_shareable(shareable)
+        def receive_shareable_visibility(user, person, shareable)
+          user.contact_for(person).receive_shareable(shareable)
           user.notify_if_mentioned(shareable)
           logger.info "event=receive payload_type=#{self.class} status=complete " \
-                      "sender=#{diaspora_handle} receiver=#{user.diaspora_handle} guid=#{shareable.guid}"
+                      "sender=#{diaspora_handle} receiver=#{person.diaspora_handle} guid=#{shareable.guid}"
         end
 
-        def receive_non_persisted(user)
+        def receive_non_persisted(user, person)
           if save
             logger.info "event=receive payload_type=#{self.class} status=complete sender=#{diaspora_handle} " \
                         "guid=#{guid}"
-            receive_shareable_visibility(user, self)
+            receive_shareable_visibility(user, person, self)
           else
             logger.warn "event=receive payload_type=#{self.class} status=abort sender=#{diaspora_handle} " \
                         "reason=#{errors.full_messages} guid=#{guid}"
@@ -122,7 +122,7 @@ module Diaspora
           logger.info "event=receive payload_type=#{self.class} status=retry sender=#{diaspora_handle} guid=#{guid}"
           local_shareable = persisted_shareable
           raise e unless local_shareable
-          receive_shareable_visibility(user, local_shareable) if verify_persisted_shareable(local_shareable)
+          receive_shareable_visibility(user, person, local_shareable) if verify_persisted_shareable(local_shareable)
         end
       end
     end
diff --git a/lib/diaspora/shareable.rb b/lib/diaspora/shareable.rb
index f0a5b31218..5c34115f8f 100644
--- a/lib/diaspora/shareable.rb
+++ b/lib/diaspora/shareable.rb
@@ -1,67 +1,55 @@
 #   Copyright (c) 2010, Diaspora Inc.  This file is
 #   licensed under the Affero General Public License version 3 or later.  See
 #   the COPYRIGHT file.
-
-# the point of this object is to centralize the simmilarities of Photo and Post,
+#the pont of this object is to centralize the simmilarities of Photo and post,
 # as they used to be the same class
 module Diaspora
   module Shareable
     def self.included(model)
       model.instance_eval do
-        has_many :aspect_visibilities, as: :shareable, validate: false
-        has_many :aspects, through: :aspect_visibilities
 
-        has_many :share_visibilities, as: :shareable, dependent: :delete_all
+        has_many :aspect_visibilities, :as => :shareable, :validate => false
+        has_many :aspects, :through => :aspect_visibilities
 
-        belongs_to :author, class_name: "Person"
+        has_many :share_visibilities, :as => :shareable
+        has_many :contacts, :through => :share_visibilities
 
-        delegate :id, :name, :first_name, to: :author, prefix: true
+        belongs_to :author, :class_name => 'Person'
 
-        # scopes
-        scope :all_public, -> { where(public: true, pending: false) }
+        delegate :id, :name, :first_name, to: :author, prefix: true
 
-        scope :with_visibility, -> {
-          joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = #{table_name}.id")
-        }
+        #scopes
+        scope :all_public, -> { where(:public => true, :pending => false) }
 
         def self.owned_or_visible_by_user(user)
-          with_visibility.where(
-            visible_by_user(user).or(arel_table[:public].eq(true)
-                                       .or(arel_table[:author_id].eq(user.person_id)))
-          ).select("DISTINCT #{table_name}.*")
-        end
-
-        def self.from_person_visible_by_user(user, person)
-          return owned_by_user(user) if person == user.person
-
-          with_visibility.where(author_id: person.id).where(
-            visible_by_user(user).or(arel_table[:public].eq(true))
-          ).select("DISTINCT #{table_name}.*")
-        end
-
-        def self.for_visible_shareable_sql(max_time, order, limit=15, types=Stream::Base::TYPES_OF_POST_IN_STREAM)
-          by_max_time(max_time, order).order(table_name + ".id DESC").where(type: types).limit(limit)
-        end
-
-        def self.by_max_time(max_time, order="created_at")
-          where("#{table_name}.#{order} < ?", max_time).order("#{table_name}.#{order} DESC")
+          self.joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = posts.id AND share_visibilities.shareable_type = 'Post'").
+               joins("LEFT OUTER JOIN contacts ON contacts.id = share_visibilities.contact_id").
+               where(
+                  Contact.arel_table[:user_id].eq(user.id).or(
+                    self.arel_table[:public].eq(true).or(
+                      self.arel_table[:author_id].eq(user.person_id)
+                   )
+                 )
+               ).
+               select("DISTINCT #{self.table_name}.*")
         end
 
-        def self.owned_by_user(user)
-          user.person.send(table_name).where(pending: false)
+        def self.for_visible_shareable_sql(max_time, order, limit = 15, types = Stream::Base::TYPES_OF_POST_IN_STREAM)
+          by_max_time(max_time, order).
+          where(:type => types).
+          limit(limit)
         end
 
-        def self.visible_by_user(user)
-          ShareVisibility.arel_table[:user_id].eq(user.id)
-            .and(ShareVisibility.arel_table[:shareable_type].eq(base_class.to_s))
+        def self.by_max_time(max_time, order='created_at')
+          where("#{self.table_name}.#{order} < ?", max_time).order("#{self.table_name}.#{order} desc")
         end
-        private_class_method :visible_by_user
       end
     end
 
     # @return [Integer]
     def update_reshares_counter
-      self.class.where(id: id).update_all(reshares_count: reshares.count)
+      self.class.where(:id => self.id).
+        update_all(:reshares_count => self.reshares.count)
     end
   end
 end
diff --git a/lib/evil_query.rb b/lib/evil_query.rb
index 9f96480bd1..20e0e66d64 100644
--- a/lib/evil_query.rb
+++ b/lib/evil_query.rb
@@ -61,7 +61,7 @@ module EvilQuery
 
     def aspects_post_ids!
       logger.debug("[EVIL-QUERY] aspect_post_ids!")
-      @user.visible_shareable_ids(Post, limit: 15, order: "#{@order} DESC", max_time: @max_time, all_aspects?: true)
+      @user.visible_shareable_ids(Post, :limit => 15, :order => "#{@order} DESC", :max_time => @max_time, :all_aspects? => true, :by_members_of => @user.aspect_ids)
     end
 
     def followed_tags_posts!
@@ -94,16 +94,13 @@ module EvilQuery
 
     def post!
       #small optimization - is this optimal order??
-      querent_has_visibility.first || querent_is_author.first || public_post.first
+      querent_is_contact.first || querent_is_author.first || public_post.first
     end
 
     protected
 
-    def querent_has_visibility
-      @class.where(@key => @id).joins(:share_visibilities)
-        .where(share_visibilities: {user_id: @querent.id})
-        .where(@conditions)
-        .select(@class.table_name + ".*")
+    def querent_is_contact
+      @class.where(@key => @id).joins(:contacts).where(:contacts => {:user_id => @querent.id}).where(@conditions).select(@class.table_name+".*")
     end
 
     def querent_is_author
@@ -114,4 +111,49 @@ module EvilQuery
       @class.where(@key => @id, :public => true).where(@conditions)
     end
   end
+
+  class ShareablesFromPerson < Base
+    def initialize(querent, klass, person)
+      @querent = querent
+      @class = klass
+      @person = person
+    end
+
+    def make_relation!
+      return querents_posts if @person == @querent.person
+
+      # persons_private_visibilities and persons_public_posts have no limit which is making shareable_ids gigantic.
+      # perhaps they should the arrays should be merged and sorted
+      # then the query at the bottom of this method can be paginated or something?
+
+      shareable_ids = contact.present? ? fetch_ids!(persons_private_visibilities, "share_visibilities.shareable_id") : []
+      shareable_ids += fetch_ids!(persons_public_posts, table_name + ".id")
+
+      @class.where(:id => shareable_ids, :pending => false).
+          select('DISTINCT '+table_name+'.*').
+          order(table_name+".created_at DESC")
+    end
+
+    protected
+
+    def table_name
+      @class.table_name
+    end
+
+    def contact
+      @contact ||= @querent.contact_for(@person)
+    end
+
+    def querents_posts
+      @querent.person.send(table_name).where(:pending => false).order("#{table_name}.created_at DESC")
+    end
+
+    def persons_private_visibilities
+      contact.share_visibilities.where(:hidden => false, :shareable_type => @class.to_s)
+    end
+
+    def persons_public_posts
+      @person.send(table_name).where(:public => true).select(table_name+'.id')
+    end
+  end
 end
diff --git a/lib/postzord/receiver/local_batch.rb b/lib/postzord/receiver/local_batch.rb
index 177e557b7b..01a6ff47fa 100644
--- a/lib/postzord/receiver/local_batch.rb
+++ b/lib/postzord/receiver/local_batch.rb
@@ -41,7 +41,8 @@ class Postzord::Receiver::LocalBatch < Postzord::Receiver
   # @note performs a bulk insert into mySQL
   # @return [void]
   def create_share_visibilities
-    ShareVisibility.batch_import(@recipient_user_ids, object)
+    contacts_ids = Contact.connection.select_values(Contact.where(:user_id => @recipient_user_ids, :person_id => @object.author_id).select("id").to_sql)
+    ShareVisibility.batch_import(contacts_ids, object)
   end
 
   # Notify any mentioned users within the @object's text
diff --git a/lib/stream/tag.rb b/lib/stream/tag.rb
index d8f3ba06b7..afb6175a27 100644
--- a/lib/stream/tag.rb
+++ b/lib/stream/tag.rb
@@ -44,11 +44,12 @@ class Stream::Tag < Stream::Base
   end
 
   def construct_post_query
-    posts = if user.present?
-              StatusMessage.owned_or_visible_by_user(user)
-            else
-              StatusMessage.all_public
-            end
+    posts = StatusMessage
+    if user.present?
+      posts = posts.owned_or_visible_by_user(user)
+    else
+      posts = posts.all_public
+    end
     posts.tagged_with(tag_name, :any => true)
   end
 end
diff --git a/spec/integration/account_deletion_spec.rb b/spec/integration/account_deletion_spec.rb
index abe3c4db28..71e52786e9 100644
--- a/spec/integration/account_deletion_spec.rb
+++ b/spec/integration/account_deletion_spec.rb
@@ -1,130 +1,134 @@
-require "spec_helper"
+require 'spec_helper'
 
-describe "deleteing your account", type: :request do
+describe 'deleteing your account', :type => :request do
   context "user" do
     before do
-      @person = bob.person
-      @alices_post = alice.post(:status_message,
-                                text: "@{bob Grimn; #{bob.person.diaspora_handle}} you are silly",
-                                to:   alice.aspects.find_by_name("generic"))
+      @bob2 = bob
+      @person = @bob2.person
+      @alices_post = alice.post(:status_message, :text => "@{@bob2 Grimn; #{@bob2.person.diaspora_handle}} you are silly", :to => alice.aspects.find_by_name('generic'))
 
-      # bob's own content
-      bob.post(:status_message, text: "asldkfjs", to: bob.aspects.first)
-      FactoryGirl.create(:photo, author: bob.person)
+      @bobs_contact_ids = @bob2.contacts.map {|c| c.id}
 
-      @aspect_vis = AspectVisibility.where(aspect_id: bob.aspects.map(&:id))
+      #@bob2's own content
+      @bob2.post(:status_message, :text => 'asldkfjs', :to => @bob2.aspects.first)
+      f = FactoryGirl.create(:photo, :author => @bob2.person)
 
-      # objects on post
-      bob.like!(@alices_post)
-      bob.comment!(@alices_post, "here are some thoughts on your post")
+      @aspect_vis = AspectVisibility.where(:aspect_id => @bob2.aspects.map(&:id))
 
-      # conversations
-      create_conversation_with_message(alice, bob.person, "Subject", "Hey bob")
+      #objects on post
+      @bob2.like!(@alices_post)
+      @bob2.comment!(@alices_post, "here are some thoughts on your post")
 
-      # join tables
-      @users_sv = ShareVisibility.where(user_id: bob.id).load
-      @persons_sv = ShareVisibility.where(shareable_id: bob.posts.map(&:id), shareable_type: "Post").load
+      #conversations
+      create_conversation_with_message(alice, @bob2.person, "Subject", "Hey @bob2")
 
-      # user associated objects
+      #join tables
+      @users_sv = ShareVisibility.where(:contact_id => @bobs_contact_ids).load
+      @persons_sv = ShareVisibility.where(:contact_id => bob.person.contacts.map(&:id)).load
+
+      #user associated objects
       @prefs = []
-      %w(mentioned liked reshared).each do |pref|
-        @prefs << bob.user_preferences.create!(email_type: pref)
+      %w{mentioned liked reshared}.each do |pref|
+        @prefs << @bob2.user_preferences.create!(:email_type => pref)
       end
 
       # notifications
       @notifications = []
-      3.times do
-        @notifications << FactoryGirl.create(:notification, recipient: bob)
+      3.times do |n|
+        @notifications << FactoryGirl.create(:notification, :recipient => @bob2)
       end
 
       # services
       @services = []
-      3.times do
-        @services << FactoryGirl.create(:service, user: bob)
+      3.times do |n|
+        @services << FactoryGirl.create(:service, :user => @bob2)
       end
 
       # block
-      @block = bob.blocks.create!(person: eve.person)
+      @block = @bob2.blocks.create!(:person => eve.person)
+
+      #authorization
 
-      AccountDeleter.new(bob.person.diaspora_handle).perform!
-      bob.reload
+      AccountDeleter.new(@bob2.person.diaspora_handle).perform!
+      @bob2.reload
     end
 
     it "deletes all of the user's preferences" do
-      expect(UserPreference.where(id: @prefs.map(&:id))).to be_empty
+      expect(UserPreference.where(:id => @prefs.map{|pref| pref.id})).to be_empty
     end
 
     it "deletes all of the user's notifications" do
-      expect(Notification.where(id: @notifications.map(&:id))).to be_empty
+      expect(Notification.where(:id => @notifications.map{|n| n.id})).to be_empty
     end
 
     it "deletes all of the users's blocked users" do
-      expect(Block.where(id: @block.id)).to be_empty
+      expect(Block.where(:id => @block.id)).to be_empty
     end
 
     it "deletes all of the user's services" do
-      expect(Service.where(id: @services.map(&:id))).to be_empty
+      expect(Service.where(:id => @services.map{|s| s.id})).to be_empty
     end
 
-    it "deletes all of bobs share visiblites" do
-      expect(ShareVisibility.where(id: @users_sv.map(&:id))).to be_empty
-      expect(ShareVisibility.where(id: @persons_sv.map(&:id))).to be_empty
+    it 'deletes all of @bob2s share visiblites' do
+      expect(ShareVisibility.where(:id => @users_sv.map{|sv| sv.id})).to be_empty
+      expect(ShareVisibility.where(:id => @persons_sv.map{|sv| sv.id})).to be_empty
     end
 
-    it "deletes all of bobs aspect visiblites" do
-      expect(AspectVisibility.where(id: @aspect_vis.map(&:id))).to be_empty
+    it 'deletes all of @bob2s aspect visiblites' do
+      expect(AspectVisibility.where(:id => @aspect_vis.map(&:id))).to be_empty
     end
 
-    it "deletes all aspects" do
-      expect(bob.aspects).to be_empty
+    it 'deletes all aspects' do
+      expect(@bob2.aspects).to be_empty
     end
 
-    it "deletes all user contacts" do
-      expect(bob.contacts).to be_empty
+    it 'deletes all user contacts' do
+      expect(@bob2.contacts).to be_empty
     end
 
+
     it "clears the account fields" do
-      bob.send(:clearable_fields).each do |field|
-        expect(bob.reload[field]).to be_blank
+      @bob2.send(:clearable_fields).each do |field|
+        expect(@bob2.reload[field]).to be_blank
       end
     end
 
-    it_should_behave_like "it removes the person associations"
+    it_should_behave_like 'it removes the person associations'
   end
 
-  context "remote person" do
+  context 'remote person' do
     before do
       @person = remote_raphael
 
-      # contacts
+      #contacts
       @contacts = @person.contacts
 
-      # posts
+      #posts
       @posts = (1..3).map do
-        FactoryGirl.create(:status_message, author: @person)
+        FactoryGirl.create(:status_message, :author => @person)
       end
 
       @persons_sv = @posts.each do |post|
         @contacts.each do |contact|
-          ShareVisibility.create!(user_id: contact.user.id, shareable: post)
+          ShareVisibility.create!(:contact_id => contact.id, :shareable => post)
         end
       end
 
-      # photos
-      @photo = FactoryGirl.create(:photo, author: @person)
+      #photos
+      @photo = FactoryGirl.create(:photo, :author => @person)
 
-      # mentions
+      #mentions
       @mentions = 3.times do
-        FactoryGirl.create(:mention, person: @person)
+        FactoryGirl.create(:mention, :person => @person)
       end
 
-      # conversations
-      create_conversation_with_message(alice, @person, "Subject", "Hey bob")
+      #conversations
+      create_conversation_with_message(alice, @person, "Subject", "Hey @bob2")
 
       AccountDeleter.new(@person.diaspora_handle).perform!
       @person.reload
     end
 
-    it_should_behave_like "it removes the person associations"
+      it_should_behave_like 'it removes the person associations'
   end
 end
diff --git a/spec/integration/receiving_spec.rb b/spec/integration/receiving_spec.rb
index e106047cb4..c62241cf7a 100644
--- a/spec/integration/receiving_spec.rb
+++ b/spec/integration/receiving_spec.rb
@@ -15,6 +15,8 @@ describe 'a user receives a post', :type => :request do
     @alices_aspect = alice.aspects.where(:name => "generic").first
     @bobs_aspect = bob.aspects.where(:name => "generic").first
     @eves_aspect = eve.aspects.where(:name => "generic").first
+
+    @contact = alice.contact_for(bob.person)
   end
 
   it 'should be able to parse and store a status message from xml' do
@@ -132,17 +134,39 @@ describe 'a user receives a post', :type => :request do
   describe 'post refs' do
     before do
       @status_message = bob.post(:status_message, :text => "hi", :to => @bobs_aspect.id)
+      alice.reload
+      @alices_aspect.reload
+      @contact = alice.contact_for(bob.person)
     end
 
-    it "adds a received post to the the user" do
+    it "adds a received post to the the contact" do
       expect(alice.visible_shareables(Post)).to include(@status_message)
-      expect(ShareVisibility.find_by(user_id: alice.id, shareable_id: @status_message.id)).not_to be_nil
+      expect(@contact.posts).to include(@status_message)
     end
 
-    it "does not remove visibility on disconnect" do
-      alice.remove_contact(alice.contact_for(bob.person), force: true)
+    it 'removes posts upon forceful removal' do
+      alice.remove_contact(@contact, :force => true)
       alice.reload
-      expect(ShareVisibility.find_by(user_id: alice.id, shareable_id: @status_message.id)).not_to be_nil
+      expect(alice.visible_shareables(Post)).not_to include @status_message
+    end
+
+    context 'dependent delete' do
+      it 'deletes share_visibilities on disconnected by' do
+        @person = FactoryGirl.create(:person)
+        alice.contacts.create(:person => @person, :aspects => [@alices_aspect])
+
+        @post = FactoryGirl.create(:status_message, :author => @person)
+        expect(@post.share_visibilities).to be_empty
+        receive_with_zord(alice, @person, @post.to_diaspora_xml)
+        @contact = alice.contact_for(@person)
+        @contact.share_visibilities.reset
+        expect(@contact.posts(true)).to include(@post)
+        @post.share_visibilities.reset
+
+        expect {
+          alice.disconnected_by(@person)
+        }.to change{@post.share_visibilities(true).count}.by(-1)
+      end
     end
   end
 
diff --git a/spec/lib/account_deleter_spec.rb b/spec/lib/account_deleter_spec.rb
index 673791c892..a8d525eb80 100644
--- a/spec/lib/account_deleter_spec.rb
+++ b/spec/lib/account_deleter_spec.rb
@@ -27,6 +27,7 @@ describe AccountDeleter do
     person_removal_methods = [:delete_contacts_of_me,
      :delete_standard_person_associations,
      :tombstone_person_and_profile,
+     :remove_share_visibilities_on_persons_posts,
      :remove_conversation_visibilities]
 
     context "user deletion" do
@@ -157,11 +158,21 @@ describe AccountDeleter do
     end
   end
 
+  describe "#remove_person_share_visibilities" do
+    it 'removes the share visibilities for a person ' do
+      @s_vis = double
+      expect(ShareVisibility).to receive(:for_contacts_of_a_person).with(bob.person).and_return(@s_vis)
+      expect(@s_vis).to receive(:destroy_all)
+
+      @account_deletion.remove_share_visibilities_on_persons_posts
+    end
+  end
+
   describe "#remove_share_visibilities_by_contacts_of_user" do
-    it "removes the share visibilities for a user" do
-      s_vis = double
-      expect(ShareVisibility).to receive(:for_a_user).with(bob).and_return(s_vis)
-      expect(s_vis).to receive(:destroy_all)
+    it 'removes the share visibilities for a user' do
+      @s_vis = double
+      expect(ShareVisibility).to receive(:for_a_users_contacts).with(bob).and_return(@s_vis)
+      expect(@s_vis).to receive(:destroy_all)
 
       @account_deletion.remove_share_visibilities_on_contacts_posts
     end
diff --git a/spec/lib/postzord/receiver/local_batch_spec.rb b/spec/lib/postzord/receiver/local_batch_spec.rb
index 5ba25c75c8..1a6c8e2fe6 100644
--- a/spec/lib/postzord/receiver/local_batch_spec.rb
+++ b/spec/lib/postzord/receiver/local_batch_spec.rb
@@ -35,7 +35,7 @@ describe Postzord::Receiver::LocalBatch do
 
   describe '#create_share_visibilities' do
     it 'calls sharevisibility.batch_import with hashes' do
-      expect(ShareVisibility).to receive(:batch_import).with(@ids, @object)
+      expect(ShareVisibility).to receive(:batch_import).with(instance_of(Array), @object)
       receiver.create_share_visibilities
     end
   end
diff --git a/spec/migrations/remove_duplicate_and_empty_pods_spec.rb b/spec/migrations/remove_duplicate_and_empty_pods_spec.rb
new file mode 100644
index 0000000000..fc26b9637f
--- /dev/null
+++ b/spec/migrations/remove_duplicate_and_empty_pods_spec.rb
@@ -0,0 +1,64 @@
+
+require "spec_helper"
+require Rails.root.join("db/migrate/20150828132451_remove_duplicate_and_empty_pods.rb")
+
+describe RemoveDuplicateAndEmptyPods do
+  self.use_transactional_fixtures = false
+
+  before do
+    @previous_migration = 20_150_731_123_114
+    @my_migration = 20_150_828_132_451
+    Pod.delete_all
+  end
+
+  after :all do
+    migrate_to(nil) # up
+    Pod.delete_all # cleanup manually, transactions are disabled
+  end
+
+  def migrate_to(version)
+    ActiveRecord::Migrator.migrate(Rails.root.join("db", "migrate"), version)
+  end
+
+  describe "#up" do
+    before do
+      migrate_to(@previous_migration)
+
+      FactoryGirl.create(:pod, host: nil)
+      FactoryGirl.create(:pod, host: "")
+      4.times { FactoryGirl.create(:pod, host: "aaa.aa") }
+    end
+
+    it "removes duplicates" do
+      expect(Pod.where(host: "aaa.aa").count).to eql(4)
+      migrate_to(@my_migration)
+      expect(Pod.where(host: "aaa.aa").count).to eql(1)
+    end
+
+    it "removes empty hostnames" do
+      expect(Pod.where(host: [nil, ""]).count).to eql(2)
+      migrate_to(@my_migration)
+      expect(Pod.where(host: [nil, ""]).count).to eql(0)
+    end
+
+    it "adds an index on the host column" do
+      expect {
+        migrate_to(@my_migration)
+        Pod.reset_column_information
+      }.to change { Pod.connection.indexes(Pod.table_name).count }.by(1)
+    end
+  end
+
+  describe "#down" do
+    before do
+      migrate_to(@my_migration)
+    end
+
+    it "removes the index on the host column" do
+      expect {
+        migrate_to(@previous_migration)
+        Pod.reset_column_information
+      }.to change { Pod.connection.indexes(Pod.table_name).count }.by(-1)
+    end
+  end
+end
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index 3a988f2a27..d35d4d19ff 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -148,22 +148,6 @@ describe Post, :type => :model do
           Post.for_visible_shareable_sql(Time.now + 1, "created_at")
         end
 
-        context "with two posts with the same timestamp" do
-          before do
-            aspect_id = alice.aspects.where(name: "generic").first.id
-            Timecop.freeze Time.now do
-              alice.post(:status_message, text: "first", to: aspect_id)
-              alice.post(:status_message, text: "second", to: aspect_id)
-            end
-          end
-
-          it "returns them in reverse creation order" do
-            posts = Post.for_visible_shareable_sql(Time.now + 1, "created_at")
-            expect(posts.first.text).to eq("second")
-            expect(posts.at(1).text).to eq("first")
-            expect(posts.last.text).to eq("alice - 5")
-          end
-        end
       end
     end
   end
@@ -271,7 +255,7 @@ describe Post, :type => :model do
     before do
       @post = FactoryGirl.create(:status_message, author: bob.person)
       @known_post = Post.new
-      allow(bob).to receive(:receive_shareable).with(@known_post).and_return(true)
+      allow(bob).to receive(:contact_for).with(eve.person).and_return(double(receive_shareable: true))
     end
 
     context "user knows about the post" do
@@ -282,13 +266,13 @@ describe Post, :type => :model do
       it "updates attributes only if mutable" do
         allow(@known_post).to receive(:mutable?).and_return(true)
         expect(@known_post).to receive(:update_attributes)
-        expect(@post.send(:receive_persisted, bob, @known_post)).to eq(true)
+        expect(@post.send(:receive_persisted, bob, eve.person, @known_post)).to eq(true)
       end
 
       it "does not update attributes if trying to update a non-mutable object" do
         allow(@known_post).to receive(:mutable?).and_return(false)
         expect(@known_post).not_to receive(:update_attributes)
-        @post.send(:receive_persisted, bob, @known_post)
+        @post.send(:receive_persisted, bob, eve.person, @known_post)
       end
     end
 
@@ -299,14 +283,14 @@ describe Post, :type => :model do
       end
 
       it "receives the post from the contact of the author" do
-        expect(@post.send(:receive_persisted, bob, @known_post)).to eq(true)
+        expect(@post.send(:receive_persisted, bob, eve.person, @known_post)).to eq(true)
       end
 
       it "notifies the user if they are mentioned" do
         allow(bob).to receive(:contact_for).with(eve.person).and_return(double(receive_shareable: true))
         expect(bob).to receive(:notify_if_mentioned).and_return(true)
 
-        expect(@post.send(:receive_persisted, bob, @known_post)).to eq(true)
+        expect(@post.send(:receive_persisted, bob, eve.person, @known_post)).to eq(true)
       end
     end
   end
@@ -320,34 +304,34 @@ describe Post, :type => :model do
       end
 
       it "it receives the post from the contact of the author" do
-        expect(bob).to receive(:receive_shareable).with(@post).and_return(true)
-        expect(@post.send(:receive_non_persisted, bob)).to eq(true)
+        expect(bob).to receive(:contact_for).with(eve.person).and_return(double(receive_shareable: true))
+        expect(@post.send(:receive_non_persisted, bob, eve.person)).to eq(true)
       end
 
       it "notifies the user if they are mentioned" do
-        allow(bob).to receive(:receive_shareable).with(@post).and_return(true)
+        allow(bob).to receive(:contact_for).with(eve.person).and_return(double(receive_shareable: true))
         expect(bob).to receive(:notify_if_mentioned).and_return(true)
 
-        expect(@post.send(:receive_non_persisted, bob)).to eq(true)
+        expect(@post.send(:receive_non_persisted, bob, eve.person)).to eq(true)
       end
 
       it "does not create shareable visibility if the post does not save" do
         allow(@post).to receive(:save).and_return(false)
         expect(@post).not_to receive(:receive_shareable_visibility)
-        @post.send(:receive_non_persisted, bob)
+        @post.send(:receive_non_persisted, bob, eve.person)
       end
 
       it "retries if saving fails with RecordNotUnique error" do
         allow(@post).to receive(:save).and_raise(ActiveRecord::RecordNotUnique.new("Duplicate entry ..."))
-        expect(bob).to receive(:receive_shareable).with(@post).and_return(true)
-        expect(@post.send(:receive_non_persisted, bob)).to eq(true)
+        expect(bob).to receive(:contact_for).with(eve.person).and_return(double(receive_shareable: true))
+        expect(@post.send(:receive_non_persisted, bob, eve.person)).to eq(true)
       end
 
       it "retries if saving fails with RecordNotUnique error and raise again if no persisted shareable found" do
         allow(@post).to receive(:save).and_raise(ActiveRecord::RecordNotUnique.new("Duplicate entry ..."))
         allow(@post).to receive(:persisted_shareable).and_return(nil)
-        expect(bob).not_to receive(:receive_shareable)
-        expect { @post.send(:receive_non_persisted, bob) }.to raise_error(ActiveRecord::RecordNotUnique)
+        expect(bob).not_to receive(:contact_for).with(eve.person)
+        expect { @post.send(:receive_non_persisted, bob, eve.person) }.to raise_error(ActiveRecord::RecordNotUnique)
       end
     end
   end
diff --git a/spec/models/share_visibility_spec.rb b/spec/models/share_visibility_spec.rb
index 39698feabf..fe2674fe3a 100644
--- a/spec/models/share_visibility_spec.rb
+++ b/spec/models/share_visibility_spec.rb
@@ -2,45 +2,55 @@
 #   licensed under the Affero General Public License version 3 or later.  See
 #   the COPYRIGHT file.
 
-require "spec_helper"
+require 'spec_helper'
 
 describe ShareVisibility, :type => :model do
-  describe ".batch_import" do
-    let(:post) { FactoryGirl.create(:status_message, author: alice.person) }
+  describe '.batch_import' do
+    before do
+      @post = FactoryGirl.create(:status_message, :author => alice.person)
+      @contact = bob.contact_for(alice.person)
+    end
 
-    it "returns false if share is public" do
-      post.public = true
-      post.save
-      expect(ShareVisibility.batch_import([bob.id], post)).to be false
+    it 'returns false if share is public' do
+      @post.public = true
+      @post.save
+      expect(ShareVisibility.batch_import([@contact.id], @post)).to be false
     end
 
-    it "creates a visibility for each user" do
+    it 'creates a visibility for each user' do
       expect {
-        ShareVisibility.batch_import([bob.id], post)
+        ShareVisibility.batch_import([@contact.id], @post)
       }.to change {
-        ShareVisibility.exists?(user_id: bob.id, shareable_id: post.id, shareable_type: "Post")
+        ShareVisibility.exists?(:contact_id => @contact.id, :shareable_id => @post.id, :shareable_type => 'Post')
       }.from(false).to(true)
     end
 
-    it "does not raise if a visibility already exists" do
-      ShareVisibility.create!(user_id: bob.id, shareable_id: post.id, shareable_type: "Post")
+    it 'does not raise if a visibility already exists' do
+      ShareVisibility.create!(:contact_id => @contact.id, :shareable_id => @post.id, :shareable_type => 'Post')
       expect {
-        ShareVisibility.batch_import([bob.id], post)
+        ShareVisibility.batch_import([@contact.id], @post)
       }.not_to raise_error
     end
 
     context "scopes" do
-      before do
-        alice.post(:status_message, text: "Hey", to: alice.aspects.first)
+      describe '.for_a_users_contacts' do
+        before do
+          alice.post(:status_message, :text => "Hey", :to => alice.aspects.first)
+        end
 
-        photo_path = File.join(File.dirname(__FILE__), "..", "fixtures", "button.png")
-        alice.post(:photo, user_file: File.open(photo_path), text: "Photo", to: alice.aspects.first)
+        it 'searches for share visibilies for all users contacts' do
+          contact_ids = alice.contacts.map(&:id)
+          expect(ShareVisibility.for_a_users_contacts(alice)).to eq(ShareVisibility.where(:contact_id => contact_ids).to_a)
+        end
       end
 
-      describe ".for_a_user" do
-        it "searches for share visibilies for a user" do
-          expect(ShareVisibility.for_a_user(bob).count).to eq(2)
-          expect(ShareVisibility.for_a_user(bob)).to eq(ShareVisibility.where(user_id: bob.id).to_a)
+      describe '.for_contacts_of_a_person' do
+        it 'searches for share visibilties generated by a person' do
+
+          contact_ids = alice.person.contacts.map(&:id)
+
+          ShareVisibility.for_contacts_of_a_person(alice.person) == ShareVisibility.where(:contact_id => contact_ids).to_a
+
         end
       end
     end
diff --git a/spec/models/user/connecting_spec.rb b/spec/models/user/connecting_spec.rb
index f33fcd2330..155dff64b1 100644
--- a/spec/models/user/connecting_spec.rb
+++ b/spec/models/user/connecting_spec.rb
@@ -82,6 +82,16 @@ describe User::Connecting, :type => :model do
     end
   end
 
+  describe '#register_share_visibilities' do
+    it 'creates post visibilites for up to 100 posts' do
+      allow(Post).to receive_message_chain(:where, :limit).and_return([FactoryGirl.create(:status_message)])
+      c = Contact.create!(:user_id => alice.id, :person_id => eve.person.id)
+      expect{
+        alice.register_share_visibilities(c)
+      }.to change(ShareVisibility, :count).by(1)
+    end
+  end
+
   describe '#share_with' do
     it 'finds or creates a contact' do
       expect {
@@ -110,6 +120,11 @@ describe User::Connecting, :type => :model do
       }.to change(contact.aspects, :count).by(1)
     end
 
+    it 'calls #register_share_visibilities with a contact' do
+      expect(eve).to receive(:register_share_visibilities)
+      eve.share_with(alice.person, eve.aspects.first)
+    end
+
     context 'dispatching' do
       it 'dispatches a request on initial request' do
         contact = alice.contacts.new(:person => eve.person)
diff --git a/spec/models/user/querying_spec.rb b/spec/models/user/querying_spec.rb
index 6adebe0024..9d8a0ff146 100644
--- a/spec/models/user/querying_spec.rb
+++ b/spec/models/user/querying_spec.rb
@@ -85,7 +85,8 @@ describe User::Querying, :type => :model do
       end
 
       it "does not pull back hidden posts" do
-        @status.share_visibilities(Post).where(user_id: alice.id).first.update_attributes(hidden: true)
+        visibility = @status.share_visibilities(Post).where(:contact_id => alice.contact_for(bob.person).id).first
+        visibility.update_attributes(:hidden => true)
         expect(alice.visible_shareable_ids(Post).include?(@status.id)).to be false
       end
     end
@@ -113,6 +114,21 @@ describe User::Querying, :type => :model do
       expect(bob.visible_shareables(Post).count(:all)).to eq(0)
     end
 
+    context 'with two posts with the same timestamp' do
+      before do
+        aspect_id = alice.aspects.where(:name => "generic").first.id
+        Timecop.freeze Time.now do
+          alice.post :status_message, :text => "first", :to => aspect_id
+          alice.post :status_message, :text => "second", :to => aspect_id
+        end
+      end
+
+      it "returns them in reverse creation order" do
+        expect(bob.visible_shareables(Post).first.text).to eq("second")
+        expect(bob.visible_shareables(Post).last.text).to eq("first")
+      end
+    end
+
     context 'with many posts' do
       before do
         time_interval = 1000
diff --git a/spec/shared_behaviors/account_deletion.rb b/spec/shared_behaviors/account_deletion.rb
index 1f4b655c11..027442b140 100644
--- a/spec/shared_behaviors/account_deletion.rb
+++ b/spec/shared_behaviors/account_deletion.rb
@@ -32,4 +32,8 @@ shared_examples_for 'it removes the person associations' do
     expect(ConversationVisibility.where(:person_id => alice.person.id)).not_to be_empty
     expect(ConversationVisibility.where(:person_id => @person.id)).to be_empty
   end
+
+  it "deletes the share visibilities on the person's posts" do
+    expect(ShareVisibility.for_contacts_of_a_person(@person)).to be_empty
+  end
 end
-- 
GitLab