diff --git a/Gemfile b/Gemfile
index 80e9f287d..d5d81356d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,7 +15,6 @@ gem 'simple_handlebars_rails', path: 'vendor/gems/simple_handlebars_rails'
 
 gem 'redcarpet', require: false
 gem 'activerecord-postgres-hstore'
-gem 'acts_as_paranoid'
 gem 'active_attr' # until we get ActiveModel::Model with Rails 4
 gem 'airbrake', '3.1.2', require: false # errbit is broken with 3.1.3 for now
 gem 'clockwork', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 8e97a4263..3532e0c3d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -120,8 +120,6 @@ GEM
     activesupport (3.2.12)
       i18n (~> 0.6)
       multi_json (~> 1.0)
-    acts_as_paranoid (0.4.1)
-      activerecord (~> 3.2)
     airbrake (3.1.2)
       activesupport
       builder
@@ -448,7 +446,6 @@ DEPENDENCIES
   active_attr
   active_model_serializers!
   activerecord-postgres-hstore
-  acts_as_paranoid
   airbrake (= 3.1.2)
   barber (= 0.3.0)
   better_errors
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 9d2bec01d..dbaf94e7f 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -33,7 +33,7 @@ class InvitesController < ApplicationController
 
     invite = Invite.where(invited_by_id: current_user.id, email: params[:email]).first
     raise Discourse::InvalidParameters.new(:email) if invite.blank?
-    invite.destroy
+    invite.trash!
 
     render nothing: true
   end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 774a4dcd5..f94381c1e 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -128,7 +128,7 @@ class PostsController < ApplicationController
   def recover
     post = find_post_from_params
     guardian.ensure_can_recover_post!(post)
-    post.recover
+    post.recover!
     render nothing: true
   end
 
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index 74014b41a..ae966f8dc 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -100,7 +100,7 @@ class TopicsController < ApplicationController
   def destroy
     topic = Topic.where(id: params[:id]).first
     guardian.ensure_can_delete!(topic)
-    topic.destroy
+    topic.trash!
     render nothing: true
   end
 
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 8427afccd..6a8c737a8 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -1,4 +1,7 @@
+require_dependency 'trashable'
+
 class Invite < ActiveRecord::Base
+  include Trashable
 
   belongs_to :user
   belongs_to :topic
@@ -9,8 +12,6 @@ class Invite < ActiveRecord::Base
   validates_presence_of :email
   validates_presence_of :invited_by_id
 
-  acts_as_paranoid
-
   before_create do
     self.invite_key ||= SecureRandom.hex
   end
diff --git a/app/models/post.rb b/app/models/post.rb
index c4500a747..d5c1724ea 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -3,19 +3,19 @@ require_dependency 'pretty_text'
 require_dependency 'rate_limiter'
 require_dependency 'post_revisor'
 require_dependency 'enum'
+require_dependency 'trashable'
 
 require 'archetype'
 require 'digest/sha1'
 
 class Post < ActiveRecord::Base
   include RateLimiter::OnCreateRecord
+  include Trashable
 
   versioned if: :raw_changed?
 
   rate_limit
-  acts_as_paranoid
 
-  after_recover :update_flagged_posts_count
 
   belongs_to :user
   belongs_to :topic, counter_cache: :posts_count
@@ -52,6 +52,11 @@ class Post < ActiveRecord::Base
     @types ||= Enum.new(:regular, :moderator_action)
   end
 
+  def recover!
+    super
+    update_flagged_posts_count
+  end
+
   def raw_quality
     sentinel = TextSentinel.body_sentinel(raw)
     errors.add(:raw, I18n.t(:is_invalid)) unless sentinel.valid?
diff --git a/app/models/post_action.rb b/app/models/post_action.rb
index 0167ad389..59b3cd12b 100644
--- a/app/models/post_action.rb
+++ b/app/models/post_action.rb
@@ -1,10 +1,12 @@
 require_dependency 'rate_limiter'
 require_dependency 'system_message'
+require_dependency 'trashable'
 
 class PostAction < ActiveRecord::Base
   class AlreadyActed < StandardError; end
 
   include RateLimiter::OnCreateRecord
+  include Trashable
 
   attr_accessible :post_action_type_id, :post_id, :user_id, :post, :user, :post_action_type, :message, :related_post_id
 
@@ -12,8 +14,6 @@ class PostAction < ActiveRecord::Base
   belongs_to :user
   belongs_to :post_action_type
 
-  acts_as_paranoid
-
   rate_limit :post_action_rate_limiter
 
   validate :message_quality
@@ -114,8 +114,7 @@ class PostAction < ActiveRecord::Base
 
   def self.remove_act(user, post, post_action_type_id)
     if action = where(post_id: post.id, user_id: user.id, post_action_type_id: post_action_type_id).first
-      action.destroy
-      action.deleted_at = Time.zone.now
+      action.trash!
       action.run_callbacks(:save)
     end
   end
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 1fa8e1d93..eef3bb19a 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -4,10 +4,12 @@ require_dependency 'topic_view'
 require_dependency 'rate_limiter'
 require_dependency 'text_sentinel'
 require_dependency 'text_cleaner'
+require_dependency 'trashable'
 
 class Topic < ActiveRecord::Base
   include ActionView::Helpers
   include RateLimiter::OnCreateRecord
+  include Trashable
 
   def self.max_sort_order
     2**31 - 1
@@ -18,9 +20,17 @@ class Topic < ActiveRecord::Base
   end
 
   versioned if: :new_version_required?
-  acts_as_paranoid
-  after_recover :update_flagged_posts_count
-  after_destroy :update_flagged_posts_count
+
+
+  def trash!
+    super
+    update_flagged_posts_count
+  end
+
+  def recover!
+    super
+    update_flagged_posts_count
+  end
 
   rate_limit :default_rate_limiter
   rate_limit :limit_topics_per_day
diff --git a/app/models/user.rb b/app/models/user.rb
index 1d70b5036..fb9f8916d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -442,11 +442,11 @@ class User < ActiveRecord::Base
 
     posts.order("post_number desc").each do |p|
       if p.post_number == 1
-        p.topic.destroy
+        p.topic.trash!
         # TODO: But the post is not destroyed. Why?
       else
         # TODO: This should be using the PostDestroyer!
-        p.destroy
+        p.trash!
       end
     end
   end
diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md
index a13d104eb..bfe79c6f5 100644
--- a/docs/SOFTWARE.md
+++ b/docs/SOFTWARE.md
@@ -14,7 +14,6 @@ The following Ruby Gems are used in Discourse:
 * [rack-mini-profiler](https://rubygems.org/gems/rack-mini-profiler)
 * [sass](https://rubygems.org/gems/sass)
 * [rest-client](https://rubygems.org/gems/rest-client)
-* [rails3_acts_as_paranoid](https://rubygems.org/gems/rails3_acts_as_paranoid)
 * [activerecord-postgres-hstore](https://rubygems.org/gems/activerecord-postgres-hstore)
 * [fastimage](https://rubygems.org/gems/fastimage)
 * [seed-fu](https://rubygems.org/gems/seed-fu)
diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb
index c5a2515b0..52c88ee35 100644
--- a/lib/post_destroyer.rb
+++ b/lib/post_destroyer.rb
@@ -37,8 +37,7 @@ class PostDestroyer
       # Feature users in the topic
       Jobs.enqueue(:feature_topic_users, topic_id: @post.topic_id, except_post_id: @post.id)
 
-      # Actually soft-delete the post :)
-      @post.destroy
+      @post.trash!
 
       Topic.reset_highest(@post.topic_id)
       @post.update_flagged_posts_count
diff --git a/lib/trashable.rb b/lib/trashable.rb
new file mode 100644
index 000000000..919545579
--- /dev/null
+++ b/lib/trashable.rb
@@ -0,0 +1,32 @@
+module Trashable
+  extend ActiveSupport::Concern
+
+  included do
+    default_scope where(with_deleted_scope_sql)
+  end
+
+
+  module ClassMethods
+    def with_deleted
+      # lifted from acts_as_paranoid, works around http://stackoverflow.com/questions/8734669/rails-3-1-3-unscoped-scope
+      scope = self.scoped.with_default_scope
+      scope.where_values.delete(with_deleted_scope_sql)
+      scope
+    end
+
+    def with_deleted_scope_sql
+      self.scoped.table[:deleted_at].eq(nil).to_sql
+    end
+  end
+
+  def trash!
+    self.update_column(:deleted_at, DateTime.now)
+  end
+
+  def recover!
+    # see: https://github.com/rails/rails/issues/8436
+    self.class.unscoped.update_all({deleted_at: nil}, id: self.id)
+    raw_write_attribute :deleted_at, nil
+  end
+
+end
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index ff9e70875..ef461a0de 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -222,14 +222,14 @@ describe Guardian do
       it 'correctly handles post visibility' do
         Guardian.new(user).can_see?(post).should be_true
 
-        post.destroy
+        post.trash!
         post.reload
         Guardian.new(user).can_see?(post).should be_false
         Guardian.new(admin).can_see?(post).should be_true
 
-        post.recover
+        post.recover!
         post.reload
-        topic.destroy
+        topic.trash!
         topic.reload
         Guardian.new(user).can_see?(post).should be_false
         Guardian.new(admin).can_see?(post).should be_true
diff --git a/spec/components/topic_view_spec.rb b/spec/components/topic_view_spec.rb
index e5eb30348..5ebf3f004 100644
--- a/spec/components/topic_view_spec.rb
+++ b/spec/components/topic_view_spec.rb
@@ -196,7 +196,7 @@ describe TopicView do
 
     describe "filter_posts_after" do
       it "returns undeleted posts after a post" do
-        topic_view.filter_posts_after(p1.post_number).should == [p2, p3, p5]
+        topic_view.filter_posts_after(p1.post_number).map(&:id).should == [p2.id, p3.id, p5.id]
         topic_view.should_not be_initial_load
         topic_view.index_offset.should == 1
         topic_view.index_reverse.should be_false
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 56feee643..473553cf2 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -29,7 +29,7 @@ describe InvitesController do
       end
 
       it "destroys the invite" do
-        Invite.any_instance.expects(:destroy)
+        Invite.any_instance.expects(:trash!)
         delete :destroy, email: invite.email
       end
 
diff --git a/spec/controllers/post_actions_controller_spec.rb b/spec/controllers/post_actions_controller_spec.rb
index a0a1dd254..29a782a7f 100644
--- a/spec/controllers/post_actions_controller_spec.rb
+++ b/spec/controllers/post_actions_controller_spec.rb
@@ -120,7 +120,7 @@ describe PostActionsController do
         end
 
         it "works with a deleted post" do
-          flagged_post.destroy
+          flagged_post.trash!
           xhr :post, :clear_flags, id: flagged_post.id, post_action_type_id: PostActionType.types[:spam]
           response.should be_success
         end
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index 9d067207f..2456d952a 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -30,7 +30,7 @@ describe PostsController do
     context "deleted post" do
 
       before do
-        post.destroy
+        post.trash!
       end
 
       it "can't find deleted posts as an anonymous user" do
@@ -121,7 +121,7 @@ describe PostsController do
       end
 
       it "calls recover" do
-        Post.any_instance.expects(:recover)
+        Post.any_instance.expects(:recover!)
         xhr :put, :recover, post_id: post.id
       end
 
diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb
index 980d75a5f..edab1bd98 100644
--- a/spec/models/post_action_spec.rb
+++ b/spec/models/post_action_spec.rb
@@ -78,7 +78,7 @@ describe PostAction do
 
     it "should reset counts when a topic is deleted" do
       PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
-      post.topic.destroy
+      post.topic.trash!
       PostAction.flagged_posts_count.should == 0
     end
 
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index 8e9be27b6..1816d3716 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -50,7 +50,7 @@ describe Post do
     let(:post) { Fabricate(:post, post_args) }
 
     before do
-      post.destroy
+      post.trash!
       post.reload
     end
 
@@ -60,7 +60,7 @@ describe Post do
 
     describe "recovery" do
       before do
-        post.recover
+        post.recover!
         post.reload
       end
 
diff --git a/spec/models/user_action_spec.rb b/spec/models/user_action_spec.rb
index 8782ffdf0..8a1c36449 100644
--- a/spec/models/user_action_spec.rb
+++ b/spec/models/user_action_spec.rb
@@ -58,7 +58,7 @@ describe UserAction do
 
       other_stats.should == expecting
 
-      public_topic.destroy
+      public_topic.trash!
       stats_for_user.should == []
       stream_count.should == 0
 
@@ -66,7 +66,7 @@ describe UserAction do
 
       category = Fabricate(:category, secure: true)
 
-      public_topic.recover
+      public_topic.recover!
       public_topic.category = category
       public_topic.save