From f23ef4488107b06ae348683ba65a4773aa58222e Mon Sep 17 00:00:00 2001 From: delba Date: Fri, 17 May 2013 18:17:18 +0200 Subject: [PATCH 01/76] use bang bang --- lib/paranoia.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 55bec047..37cf1854 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -28,7 +28,7 @@ def restore! end def destroyed? - !self.deleted_at.nil? + !!deleted_at end alias :deleted? :destroyed? From 18dd3f18755107c268cb419ea8df212817e37079 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 2 Jul 2013 16:50:12 -0400 Subject: [PATCH 02/76] Formatting and strings --- lib/paranoia.rb | 20 ++++++++++++++------ lib/paranoia/version.rb | 2 +- paranoia.gemspec | 22 +++++++++++----------- test/paranoia_test.rb | 14 +++++++------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 19cca058..5c3a1aaa 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -4,10 +4,12 @@ def self.included(klazz) end module Query - def paranoid? ; true ; end + def paranoid? + true + end def only_deleted - scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at is not null") + scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") end def with_deleted @@ -31,6 +33,7 @@ def restore! def destroyed? !self.deleted_at.nil? end + alias :deleted? :destroyed? private @@ -44,13 +47,18 @@ def update_attribute_or_column(*args) class ActiveRecord::Base def self.acts_as_paranoid alias :destroy! :destroy - alias :delete! :delete + alias :delete! :delete include Paranoia - default_scope { where(:deleted_at => nil) } + default_scope { where(deleted_at: nil) } end - def self.paranoid? ; false ; end - def paranoid? ; self.class.paranoid? ; end + def self.paranoid? + false + end + + def paranoid? + self.class.paranoid? + end # Override the persisted method to allow for the paranoia gem. # If a paranoid record is selected, then we only want to check diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index 19fc57a4..0d104a2c 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = "1.2.0" + VERSION = '1.2.0' end diff --git a/paranoia.gemspec b/paranoia.gemspec index bc26477b..0abdf005 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -1,24 +1,24 @@ # -*- encoding: utf-8 -*- -require File.expand_path("../lib/paranoia/version", __FILE__) +require File.expand_path('../lib/paranoia/version', __FILE__) Gem::Specification.new do |s| - s.name = "paranoia" + s.name = 'paranoia' s.version = Paranoia::VERSION s.platform = Gem::Platform::RUBY - s.authors = ["radarlistener@gmail.com"] + s.authors = %w(radarlistener@gmail.com) s.email = [] - s.homepage = "http://rubygems.org/gems/paranoia" - s.summary = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code." + s.homepage = 'http://rubygems.org/gems/paranoia' + s.summary = 'Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code.' s.description = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code. You would use either plugin / gem if you wished that when you called destroy on an Active Record object that it didn't actually destroy it, but just \"hid\" the record. Paranoia does this by setting a deleted_at field to the current time when you destroy a record, and hides it by scoping all queries on your model to only include records which do not have a deleted_at field." - s.required_rubygems_version = ">= 1.3.6" - s.rubyforge_project = "paranoia" + s.required_rubygems_version = '>= 1.3.6' + s.rubyforge_project = 'paranoia' - s.add_dependency "activerecord", ">= 3.1.0" + s.add_dependency 'activerecord', '>= 3.1.0' - s.add_development_dependency "bundler", ">= 1.0.0" - s.add_development_dependency "sqlite3" - s.add_development_dependency "rake", "0.8.7" + s.add_development_dependency 'bundler', '>= 1.0.0' + s.add_development_dependency 'sqlite3' + s.add_development_dependency 'rake', '0.8.7' s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 9e763ea2..21a6f6fa 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -1,6 +1,6 @@ require 'test/unit' require 'active_record' -require File.expand_path(File.dirname(__FILE__) + "/../lib/paranoia") +require File.expand_path(File.dirname(__FILE__) + '/../lib/paranoia') DB_FILE = 'tmp/test_db' @@ -71,7 +71,7 @@ def test_destroy_behavior_for_paranoid_models assert_equal 0, model.class.count assert_equal 1, model.class.unscoped.count end - + def test_scoping_behavior_for_paranoid_models ParanoidModel.unscoped.delete_all parent1 = ParentModel.create @@ -84,7 +84,7 @@ def test_scoping_behavior_for_paranoid_models assert_equal 1, parent1.paranoid_models.only_deleted.count p3 = ParanoidModel.create(:parent_model => parent1) assert_equal 2, parent1.paranoid_models.with_deleted.count - assert_equal [p1,p3], parent1.paranoid_models.with_deleted + assert_equal [p1, p3], parent1.paranoid_models.with_deleted end def test_destroy_behavior_for_featureful_paranoid_models @@ -102,8 +102,8 @@ def test_destroy_behavior_for_featureful_paranoid_models # Regression test for #24 def test_chaining_for_paranoid_models - scope = FeaturefulModel.where(:name => "foo").only_deleted - assert_equal "foo", scope.where_values_hash[:name] + scope = FeaturefulModel.where(:name => 'foo').only_deleted + assert_equal 'foo', scope.where_values_hash[:name] assert_equal 2, scope.where_values.count end @@ -206,7 +206,7 @@ def test_real_delete private def get_featureful_model - FeaturefulModel.new(:name => "not empty") + FeaturefulModel.new(:name => 'not empty') end end @@ -231,7 +231,7 @@ class PlainModel < ActiveRecord::Base class CallbackModel < ActiveRecord::Base acts_as_paranoid - before_destroy {|model| model.instance_variable_set :@callback_called, true } + before_destroy { |model| model.instance_variable_set :@callback_called, true } end class ParentModel < ActiveRecord::Base From 7bbd9a8976d644e67ac0f0714feb3cc728d96eea Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 2 Jul 2013 17:00:06 -0400 Subject: [PATCH 03/76] Upgrade some gems --- paranoia.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paranoia.gemspec b/paranoia.gemspec index 0abdf005..2d645928 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'sqlite3' - s.add_development_dependency 'rake', '0.8.7' + s.add_development_dependency 'rake' s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact From 434a0d98fbb900ac5f96e91a47607dcd16e78964 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 2 Jul 2013 17:00:12 -0400 Subject: [PATCH 04/76] Use all instead of scoped --- lib/paranoia.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 5c3a1aaa..5fee8d93 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -9,11 +9,11 @@ def paranoid? end def only_deleted - scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") + all.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") end def with_deleted - scoped.tap { |x| x.default_scoped = false } + all.tap { |x| x.default_scoped = false } end end From fda5b32bca4ba5495e2926afa2a2b420fd41985b Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 2 Jul 2013 17:25:31 -0400 Subject: [PATCH 05/76] Fix broken tests --- paranoia.gemspec | 25 +++++++++++++------------ test/paranoia_test.rb | 8 ++++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/paranoia.gemspec b/paranoia.gemspec index 2d645928..18dd4893 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -2,25 +2,26 @@ require File.expand_path('../lib/paranoia/version', __FILE__) Gem::Specification.new do |s| - s.name = 'paranoia' - s.version = Paranoia::VERSION - s.platform = Gem::Platform::RUBY - s.authors = %w(radarlistener@gmail.com) - s.email = [] - s.homepage = 'http://rubygems.org/gems/paranoia' - s.summary = 'Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code.' + s.name = 'paranoia' + s.version = Paranoia::VERSION + s.platform = Gem::Platform::RUBY + s.authors = %w(radarlistener@gmail.com) + s.email = [] + s.homepage = 'http://rubygems.org/gems/paranoia' + s.summary = 'Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code.' s.description = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code. You would use either plugin / gem if you wished that when you called destroy on an Active Record object that it didn't actually destroy it, but just \"hid\" the record. Paranoia does this by setting a deleted_at field to the current time when you destroy a record, and hides it by scoping all queries on your model to only include records which do not have a deleted_at field." s.required_rubygems_version = '>= 1.3.6' - s.rubyforge_project = 'paranoia' - + s.rubyforge_project = 'paranoia' + s.add_dependency 'activerecord', '>= 3.1.0' s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'sqlite3' s.add_development_dependency 'rake' - - s.files = `git ls-files`.split("\n") - s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact + s.add_development_dependency 'debugger' + + s.files = `git ls-files`.split("\n") + s.executables = `git ls-files`.split("\n").map { |f| f =~ /^bin\/(.*)/ ? $1 : nil }.compact s.require_path = 'lib' end diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 21a6f6fa..b5023eeb 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -1,5 +1,6 @@ require 'test/unit' require 'active_record' +require 'debugger' require File.expand_path(File.dirname(__FILE__) + '/../lib/paranoia') DB_FILE = 'tmp/test_db' @@ -103,7 +104,7 @@ def test_destroy_behavior_for_featureful_paranoid_models # Regression test for #24 def test_chaining_for_paranoid_models scope = FeaturefulModel.where(:name => 'foo').only_deleted - assert_equal 'foo', scope.where_values_hash[:name] + assert_equal 'foo', scope.where_values_hash['name'] assert_equal 2, scope.where_values.count end @@ -193,15 +194,14 @@ def test_real_destroy model.save model.destroy! - assert_equal false, ParanoidModel.unscoped.exists?(model.id) + assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end def test_real_delete model = ParanoidModel.new model.save model.delete! - - assert_equal false, ParanoidModel.unscoped.exists?(model.id) + assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end private From 52946b94752c9e773cf1641b4ba7fba459e47a1c Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 2 Jul 2013 17:28:33 -0400 Subject: [PATCH 06/76] Add travis ruby versions --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..418ef341 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 \ No newline at end of file From bcbd4d98b16513e596f55fcf0a8716ca34be6d89 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:01:13 +1000 Subject: [PATCH 07/76] Use https://rubygems.org for Gemfile source --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 1a354a68..d32360e9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source :gemcutter +source 'https://rubygems.org' # Specify your gem's dependencies in paranoia.gemspec gemspec From 4e48a7830f8f8f6c3c3849d4c8ee40aafc3eba60 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:01:22 +1000 Subject: [PATCH 08/76] Force dependency on only AR 3.2 for master branch --- paranoia.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paranoia.gemspec b/paranoia.gemspec index bc26477b..1728695e 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "paranoia" - s.add_dependency "activerecord", ">= 3.1.0" + s.add_dependency "activerecord", "~> 3.2" s.add_development_dependency "bundler", ">= 1.0.0" s.add_development_dependency "sqlite3" From e384eff484826cf4a015e566a732fbc86ff20578 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:08:10 +1000 Subject: [PATCH 09/76] Use #all because #scoped is deprecated --- lib/paranoia.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 19cca058..a544b55a 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -7,11 +7,11 @@ module Query def paranoid? ; true ; end def only_deleted - scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at is not null") + all.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at is not null") end def with_deleted - scoped.tap { |x| x.default_scoped = false } + all.tap { |x| x.default_scoped = false } end end From cd02d0dd091e7d7babf89dd24ace0f2493217f4a Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:08:18 +1000 Subject: [PATCH 10/76] Add pry for debugging purposes --- paranoia.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/paranoia.gemspec b/paranoia.gemspec index 1728695e..5dbb8ce3 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |s| s.add_development_dependency "bundler", ">= 1.0.0" s.add_development_dependency "sqlite3" s.add_development_dependency "rake", "0.8.7" + s.add_development_dependency "pry" s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact From 1e703ba24a7afc9ede0563ffcc0b4d57f098b3d6 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:08:26 +1000 Subject: [PATCH 11/76] Use string for where_values_hash rather than symbol --- test/paranoia_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 9e763ea2..390c62e6 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -103,7 +103,8 @@ def test_destroy_behavior_for_featureful_paranoid_models # Regression test for #24 def test_chaining_for_paranoid_models scope = FeaturefulModel.where(:name => "foo").only_deleted - assert_equal "foo", scope.where_values_hash[:name] + + assert_equal "foo", scope.where_values_hash["name"] assert_equal 2, scope.where_values.count end From e1d8ea15c8e6dca34f7cd2d2920da92262833388 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:09:00 +1000 Subject: [PATCH 12/76] exists? in AR now returns nil when a record cannot be found. --- test/paranoia_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 390c62e6..951a03f9 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -194,7 +194,7 @@ def test_real_destroy model.save model.destroy! - assert_equal false, ParanoidModel.unscoped.exists?(model.id) + assert_equal false, !!ParanoidModel.unscoped.exists?(model.id) end def test_real_delete @@ -202,7 +202,7 @@ def test_real_delete model.save model.delete! - assert_equal false, ParanoidModel.unscoped.exists?(model.id) + assert_equal false, !!ParanoidModel.unscoped.exists?(model.id) end private From a2db597548bf42d665d52b6eb7e88b125bc51ecb Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:09:38 +1000 Subject: [PATCH 13/76] Bump to 1.3.0 --- lib/paranoia/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index 19fc57a4..8802cda5 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = "1.2.0" + VERSION = "1.3.0" end From 366c3e104000aeea67fdb9af4b69fec072ae7cae Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 12:12:51 +1000 Subject: [PATCH 14/76] Update README with rails4 instructions --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0090241c..3bcd19fd 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,24 @@ You would use either plugin / gem if you wished that when you called `destroy` o ## Installation & Usage -Put this in your Gemfile: +For Rails 3, please use version 1 of Paranoia: ```ruby -gem 'paranoia' +gem 'paranoia', '~> 1.0' +``` + +For Rails 4, please use version 2 of Paranoia: + +```ruby +gem 'paranoia', '~> 2.0' +``` + +Of course you can install this from GitHub as well: + +```ruby +gem 'paranoia', :github => 'radar/paranoia', :branch => 'master' +# or +gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails-4' ``` Then run: From cd7bd20a5ec89e97e9d2a63cfe043e01aa7d9fa8 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 14:24:02 +1000 Subject: [PATCH 15/76] Use 'scoped', not 'all' in only_deleted and with_deleted --- lib/paranoia.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index a544b55a..19cca058 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -7,11 +7,11 @@ module Query def paranoid? ; true ; end def only_deleted - all.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at is not null") + scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at is not null") end def with_deleted - all.tap { |x| x.default_scoped = false } + scoped.tap { |x| x.default_scoped = false } end end From 6670dce6b307679e4273f58de96fb240528b46c8 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 8 Jul 2013 14:26:21 +1000 Subject: [PATCH 16/76] Bump to 1.3.1 --- lib/paranoia/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index 8802cda5..d075170d 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = "1.3.0" + VERSION = "1.3.1" end From 6420d2655c741a979208eae3dae3d25d5c43ffed Mon Sep 17 00:00:00 2001 From: delba Date: Tue, 9 Jul 2013 11:40:44 +0200 Subject: [PATCH 17/76] change git branch rails-4 to rails4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bcd19fd..7341e937 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Of course you can install this from GitHub as well: ```ruby gem 'paranoia', :github => 'radar/paranoia', :branch => 'master' # or -gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails-4' +gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails4' ``` Then run: From c7f2a726d644c27e1664c040109025429b4f5e00 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Thu, 11 Jul 2013 11:43:53 -0400 Subject: [PATCH 18/76] Add aliases to sync with mongoid paranoia --- lib/paranoia.rb | 2 ++ test/paranoia_test.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 5fee8d93..f2b3b082 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -11,6 +11,7 @@ def paranoid? def only_deleted all.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") end + alias :deleted :only_deleted def with_deleted all.tap { |x| x.default_scoped = false } @@ -29,6 +30,7 @@ def delete def restore! update_attribute_or_column :deleted_at, nil end + alias :restore :restore! def destroyed? !self.deleted_at.nil? diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index b5023eeb..149de025 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -83,6 +83,7 @@ def test_scoping_behavior_for_paranoid_models p2.destroy assert_equal 0, parent1.paranoid_models.count assert_equal 1, parent1.paranoid_models.only_deleted.count + assert_equal 1, parent1.paranoid_models.deleted.count p3 = ParanoidModel.create(:parent_model => parent1) assert_equal 2, parent1.paranoid_models.with_deleted.count assert_equal [p1, p3], parent1.paranoid_models.with_deleted @@ -117,6 +118,7 @@ def test_only_destroyed_scope_for_paranoid_models assert_equal model, ParanoidModel.only_deleted.last assert_equal false, ParanoidModel.only_deleted.include?(model2) + assert_equal false, ParanoidModel.deleted.include?(model2) end def test_default_scope_for_has_many_relationships From c27a5c132a62fe11cf296fbaea42ed744ec5b5c8 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Thu, 11 Jul 2013 11:49:28 -0400 Subject: [PATCH 19/76] Fix conflict merges --- lib/paranoia/version.rb | 2 +- paranoia.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index 0d104a2c..c5d3eea1 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = '1.2.0' + VERSION = '1.3.1' end diff --git a/paranoia.gemspec b/paranoia.gemspec index 18dd4893..d19d0cf8 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = '>= 1.3.6' s.rubyforge_project = 'paranoia' - s.add_dependency 'activerecord', '>= 3.1.0' + s.add_dependency 'activerecord', '~> 3.2' s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'sqlite3' From 2d79e5734e531f133a6974ff89b279f901bdd0e4 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Thu, 11 Jul 2013 12:18:52 -0400 Subject: [PATCH 20/76] Fix scoped vs all --- lib/paranoia.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index f2b3b082..552db638 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -9,12 +9,12 @@ def paranoid? end def only_deleted - all.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") + scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") end alias :deleted :only_deleted def with_deleted - all.tap { |x| x.default_scoped = false } + scoped.tap { |x| x.default_scoped = false } end end From 77734188bd8e7d3879f1f8d62881c6c950eb4990 Mon Sep 17 00:00:00 2001 From: John Gerhardt Date: Tue, 16 Jul 2013 09:11:40 -0400 Subject: [PATCH 21/76] Remove debugger --- paranoia.gemspec | 1 - test/paranoia_test.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/paranoia.gemspec b/paranoia.gemspec index d19d0cf8..ecc6b81b 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -19,7 +19,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'sqlite3' s.add_development_dependency 'rake' - s.add_development_dependency 'debugger' s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map { |f| f =~ /^bin\/(.*)/ ? $1 : nil }.compact diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 149de025..158271ac 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -1,6 +1,5 @@ require 'test/unit' require 'active_record' -require 'debugger' require File.expand_path(File.dirname(__FILE__) + '/../lib/paranoia') DB_FILE = 'tmp/test_db' From 11cf0f19cf0eb698aabc8e201f7fbeb184d4474a Mon Sep 17 00:00:00 2001 From: Dan Cheail Date: Mon, 9 Sep 2013 16:51:41 +1000 Subject: [PATCH 22/76] Add Model.restore(ids) function Modelled on AR::Relation.destroy Fixes #69 --- lib/paranoia.rb | 8 ++++++++ test/paranoia_test.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 552db638..3c16d417 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -16,6 +16,14 @@ def only_deleted def with_deleted scoped.tap { |x| x.default_scoped = false } end + + def restore(id) + if id.is_a?(Array) + id.map { |one_id| restore(one_id) } + else + only_deleted.find(id).restore! + end + end end def destroy diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 158271ac..f9b08c71 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -205,6 +205,33 @@ def test_real_delete assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end + def test_multiple_restore + a = ParanoidModel.new + a.save + a_id = a.id + a.destroy + + b = ParanoidModel.new + b.save + b_id = b.id + b.destroy + + c = ParanoidModel.new + c.save + c_id = c.id + c.destroy + + ParanoidModel.restore([a_id, c_id]) + + a.reload + b.reload + c.reload + + refute a.destroyed? + assert b.destroyed? + refute c.destroyed? + end + private def get_featureful_model FeaturefulModel.new(:name => 'not empty') From 0914990c02fa80b613c7aaef6610c60332714c40 Mon Sep 17 00:00:00 2001 From: James Strong Date: Mon, 16 Sep 2013 19:39:43 -0500 Subject: [PATCH 23/76] Fix issue with default scope clashing with deletion. "update_column" uses default scopes which can collide with deletion. --- lib/paranoia.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 3c16d417..32476d53 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -50,7 +50,9 @@ def destroyed? # Rails 3.1 adds update_column. Rails > 3.2.6 deprecates update_attribute, gone in Rails 4. def update_attribute_or_column(*args) - respond_to?(:update_column) ? update_column(*args) : update_attribute(*args) + self.class.unscoped do + respond_to?(:update_column) ? update_column(*args) : update_attribute(*args) + end end end From 9c5cd2b64fc834dbecbeceadeb1bb26845039f1b Mon Sep 17 00:00:00 2001 From: Dobaczewski Date: Thu, 3 Oct 2013 18:53:45 -0700 Subject: [PATCH 24/76] destroy! when destroying deleted? records --- lib/paranoia.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 32476d53..c4a3e941 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -31,8 +31,8 @@ def destroy end def delete - return if new_record? or destroyed? - update_attribute_or_column :deleted_at, Time.now + return if new_record? + destroyed? ? destroy! : update_attribute_or_column(:deleted_at, Time.now) end def restore! From 4ece33f8d70458fba9d8a99df71176457757d2af Mon Sep 17 00:00:00 2001 From: Michal Dobaczewski Date: Tue, 8 Oct 2013 22:28:07 -0700 Subject: [PATCH 25/76] Add test for destroy of a deleted? object --- test/paranoia_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index f9b08c71..1b7d0dbe 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -190,6 +190,15 @@ def test_restore assert_equal false, model.destroyed? end + def test_destroy_twice + model = ParanoidModel.new + model.save + model.destroy + model.destroy + + assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count + end + def test_real_destroy model = ParanoidModel.new model.save From 8d36866c6483500ee82129cd42c86fdb2afe9b25 Mon Sep 17 00:00:00 2001 From: Michal Dobaczewski Date: Tue, 8 Oct 2013 22:28:52 -0700 Subject: [PATCH 26/76] Add description of destroy! behavior to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7341e937..d2f3c3d5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/techno You would use either plugin / gem if you wished that when you called `destroy` on an Active Record object that it didn't actually destroy it, but just "hid" the record. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field. +If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object. + ## Installation & Usage For Rails 3, please use version 1 of Paranoia: From 714fb9f0b802c26f660f2170b7178a1b2be5360c Mon Sep 17 00:00:00 2001 From: Michael Dilley Date: Thu, 17 Oct 2013 17:04:58 +1100 Subject: [PATCH 27/76] Add callbacks for 'new' AR lifecycle :restore Fixes #72 --- lib/paranoia.rb | 21 ++++++++++++++++++++- test/paranoia_test.rb | 19 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index c4a3e941..5309dc31 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -1,6 +1,7 @@ module Paranoia def self.included(klazz) klazz.extend Query + klazz.extend Callbacks end module Query @@ -26,6 +27,24 @@ def restore(id) end end + module Callbacks + def self.extended(klazz) + klazz.define_callbacks :restore + + klazz.define_singleton_method("before_restore") do |*args, &block| + set_callback(:restore, :before, *args, &block) + end + + klazz.define_singleton_method("around_restore") do |*args, &block| + set_callback(:restore, :around, *args, &block) + end + + klazz.define_singleton_method("after_restore") do |*args, &block| + set_callback(:restore, :after, *args, &block) + end + end + end + def destroy run_callbacks(:destroy) { delete } end @@ -36,7 +55,7 @@ def delete end def restore! - update_attribute_or_column :deleted_at, nil + run_callbacks(:restore) { update_column :deleted_at, nil } end alias :restore :restore! diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 1b7d0dbe..b2281d6b 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -199,6 +199,22 @@ def test_destroy_twice assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end + def test_restore_behavior_for_callbacks + model = CallbackModel.new + model.save + id = model.id + model.destroy + + assert model.destroyed? + + model = CallbackModel.only_deleted.find(id) + model.restore! + model.reload + + assert model.instance_variable_get(:@restore_callback_called) +>>>>>>> Add callbacks for 'new' AR lifecycle :restore + end + def test_real_destroy model = ParanoidModel.new model.save @@ -268,7 +284,8 @@ class PlainModel < ActiveRecord::Base class CallbackModel < ActiveRecord::Base acts_as_paranoid - before_destroy { |model| model.instance_variable_set :@callback_called, true } + before_destroy {|model| model.instance_variable_set :@callback_called, true } + before_restore {|model| model.instance_variable_set :@restore_callback_called, true } end class ParentModel < ActiveRecord::Base From 378b51ce3f0ebbdad401531d4f5ee9327983f58f Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 21 Oct 2013 09:43:32 +1100 Subject: [PATCH 28/76] Remove conflict from paranoia_test --- test/paranoia_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index b2281d6b..41496cd9 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -212,7 +212,6 @@ def test_restore_behavior_for_callbacks model.reload assert model.instance_variable_get(:@restore_callback_called) ->>>>>>> Add callbacks for 'new' AR lifecycle :restore end def test_real_destroy From fbf95d14957b8a3ec662fb7d1cc9232b5aeb4fb6 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 24 Oct 2013 16:20:53 +1100 Subject: [PATCH 29/76] Reuse with_deleted for only_deleted Applies changes suggested in #59 which did not apply correctly --- lib/paranoia.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 951da598..c0de7503 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -9,15 +9,16 @@ def paranoid? true end - def only_deleted - scoped.tap { |x| x.default_scoped = false }.where("#{self.table_name}.deleted_at IS NOT NULL") - end - alias :deleted :only_deleted def with_deleted scoped.tap { |x| x.default_scoped = false } end + def only_deleted + with_deleted.where("#{self.table_name}.deleted_at IS NOT NULL") + end + alias :deleted :only_deleted + def restore(id) if id.is_a?(Array) id.map { |one_id| restore(one_id) } From f57695be2b3cce71836e57ccb0f86f5cacd28902 Mon Sep 17 00:00:00 2001 From: Sean O'Hara Date: Wed, 23 Oct 2013 17:18:52 -0400 Subject: [PATCH 30/76] Use quoted table name in default scope (call me paranoid) Fixes #73 --- lib/paranoia.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index c0de7503..7742eaed 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -81,7 +81,7 @@ def self.acts_as_paranoid alias :destroy! :destroy alias :delete! :delete include Paranoia - default_scope { where(deleted_at: nil) } + default_scope { where(self.quoted_table_name + '.deleted_at IS NULL') } end def self.paranoid? From ffa40a244502570b02228fc8afed8074e347a843 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 24 Oct 2013 16:27:55 +1100 Subject: [PATCH 31/76] Bump to 1.3.2 --- lib/paranoia/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index c5d3eea1..8a4ae1d2 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = '1.3.1' + VERSION = '1.3.2' end From b81b3af1fafd2545b4a466801c4e745c0d41d8cf Mon Sep 17 00:00:00 2001 From: Jake Quain Date: Thu, 24 Oct 2013 13:57:03 -0400 Subject: [PATCH 32/76] Add configuration option for column name References: https://github.com/goncalossilva/acts_as_paranoid Fixes #76 Conflicts: lib/paranoia.rb --- README.md | 10 ++++++++++ lib/paranoia.rb | 21 +++++++++++++++------ test/paranoia_test.rb | 22 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d2f3c3d5..258a3fd0 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,16 @@ class Client < ActiveRecord::Base end ``` +If you want to use a column other than deleted_at, you can pass it as an option: + +```ruby +class Client < ActiveRecord::Base + acts_as_paranoid column: :destroyed_at + + ... +end +``` + You can replace the older acts_as_paranoid methods as follows: | Old Syntax | New Syntax | diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 7742eaed..00ef2791 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -15,7 +15,7 @@ def with_deleted end def only_deleted - with_deleted.where("#{self.table_name}.deleted_at IS NOT NULL") + with_deleted.where("#{self.table_name}.#{paranoia_column} IS NOT NULL") end alias :deleted :only_deleted @@ -52,16 +52,16 @@ def destroy def delete return if new_record? - destroyed? ? destroy! : update_attribute_or_column(:deleted_at, Time.now) + destroyed? ? destroy! : update_attribute_or_column(paranoia_column, Time.now) end def restore! - run_callbacks(:restore) { update_column :deleted_at, nil } + run_callbacks(:restore) { update_column paranoia_column, nil } end alias :restore :restore! def destroyed? - !!deleted_at + !!send(paranoia_column) end alias :deleted? :destroyed? @@ -77,11 +77,14 @@ def update_attribute_or_column(*args) end class ActiveRecord::Base - def self.acts_as_paranoid + def self.acts_as_paranoid(options={}) alias :destroy! :destroy alias :delete! :delete include Paranoia - default_scope { where(self.quoted_table_name + '.deleted_at IS NULL') } + class_attribute :paranoia_column + + self.paranoia_column = options[:column] || :deleted_at + default_scope { where(self.quoted_table_name + ".#{paranoia_column} IS NULL") } end def self.paranoid? @@ -98,4 +101,10 @@ def paranoid? def persisted? paranoid? ? !new_record? : super end + + private + + def paranoia_column + self.class.paranoia_column + end end diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 41496cd9..9cac8301 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -17,6 +17,7 @@ ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)' +ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)' class ParanoiaTest < Test::Unit::TestCase def test_plain_model_class_is_not_paranoid @@ -88,6 +89,23 @@ def test_scoping_behavior_for_paranoid_models assert_equal [p1, p3], parent1.paranoid_models.with_deleted end + def test_destroy_behavior_for_custom_column_models + model = CustomColumnModel.new + assert_equal 0, model.class.count + model.save! + assert_nil model.destroyed_at + assert_equal 1, model.class.count + model.destroy + + assert_equal false, model.destroyed_at.nil? + assert model.destroyed? + + assert_equal 0, model.class.count + assert_equal 1, model.class.unscoped.count + assert_equal 1, model.class.only_deleted.count + assert_equal 1, model.class.deleted.count + end + def test_destroy_behavior_for_featureful_paranoid_models model = get_featureful_model assert_equal 0, model.class.count @@ -314,3 +332,7 @@ class Job < ActiveRecord::Base belongs_to :employer belongs_to :employee end + +class CustomColumnModel < ActiveRecord::Base + acts_as_paranoid column: :destroyed_at +end From d28a4a0313ec9cedb7b02671a1f47318b5b2681e Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 6 Nov 2013 10:16:47 +1100 Subject: [PATCH 33/76] Add anti-regression test for #81 Update/save callbacks should not be called at all during a destroy call, as would happen with the code from that PR. --- test/paranoia_test.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 9cac8301..1c83df80 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -60,6 +60,14 @@ def test_destroy_behavior_for_plain_models assert_equal 0, model.class.unscoped.count end + # Anti-regression test for #81, which would've introduced a bug to break this test. + def test_destroy_behavior_for_plain_models_callbacks + model = CallbackModel.new + model.destroy + assert_equal nil, model.instance_variable_get(:@update_callback_called) + assert_equal nil, model.instance_variable_get(:@save_callback_called) + end + def test_destroy_behavior_for_paranoid_models model = ParanoidModel.new assert_equal 0, model.class.count @@ -183,14 +191,14 @@ def test_delete_behavior_for_callbacks model = CallbackModel.new model.save model.delete - assert_equal nil, model.instance_variable_get(:@callback_called) + assert_equal nil, model.instance_variable_get(:@destroy_callback_called) end def test_destroy_behavior_for_callbacks model = CallbackModel.new model.save model.destroy - assert model.instance_variable_get(:@callback_called) + assert model.instance_variable_get(:@destroy_callback_called) end def test_restore @@ -301,8 +309,10 @@ class PlainModel < ActiveRecord::Base class CallbackModel < ActiveRecord::Base acts_as_paranoid - before_destroy {|model| model.instance_variable_set :@callback_called, true } + before_destroy {|model| model.instance_variable_set :@destroy_callback_called, true } before_restore {|model| model.instance_variable_set :@restore_callback_called, true } + before_update {|model| model.instance_variable_set :@update_callback_called, true } + before_save {|model| model.instance_variable_set :@save_callback_called, true} end class ParentModel < ActiveRecord::Base From 0af156c3b959016b37f0a94f2da63b4be0ff1659 Mon Sep 17 00:00:00 2001 From: alfa-jpn Date: Wed, 6 Nov 2013 12:15:29 +0900 Subject: [PATCH 34/76] add tests for plain models callback Fixes #81 Fixes #84 --- lib/paranoia.rb | 21 +++++++++++++++------ test/paranoia_test.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 00ef2791..0b43af79 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -47,12 +47,12 @@ def self.extended(klazz) end def destroy - run_callbacks(:destroy) { delete } + run_callbacks(:destroy) { delete_or_soft_delete(true) } end def delete return if new_record? - destroyed? ? destroy! : update_attribute_or_column(paranoia_column, Time.now) + delete_or_soft_delete end def restore! @@ -67,11 +67,20 @@ def destroyed? alias :deleted? :destroyed? private + # select and exec delete or soft-delete. + # @param with_transaction [Boolean] exec with ActiveRecord Transactions, when soft-delete. + def delete_or_soft_delete(with_transaction=false) + destroyed? ? destroy! : touch_paranoia_column(with_transaction) + end - # Rails 3.1 adds update_column. Rails > 3.2.6 deprecates update_attribute, gone in Rails 4. - def update_attribute_or_column(*args) - self.class.unscoped do - respond_to?(:update_column) ? update_column(*args) : update_attribute(*args) + # touch paranoia column. + # insert time to paranoia column. + # @param with_transaction [Boolean] exec with ActiveRecord Transactions. + def touch_paranoia_column(with_transaction=false) + if with_transaction + with_transaction_returning_status { touch(paranoia_column) } + else + touch(paranoia_column) end end end diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 1c83df80..f5c187d8 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -63,9 +63,32 @@ def test_destroy_behavior_for_plain_models # Anti-regression test for #81, which would've introduced a bug to break this test. def test_destroy_behavior_for_plain_models_callbacks model = CallbackModel.new + model.save + model.remove_called_variables # clear called callback flags model.destroy + + assert_equal nil, model.instance_variable_get(:@update_callback_called) + assert_equal nil, model.instance_variable_get(:@save_callback_called) + assert_equal nil, model.instance_variable_get(:@validate_called) + + assert model.instance_variable_get(:@destroy_callback_called) + assert model.instance_variable_get(:@after_destroy_callback_called) + assert model.instance_variable_get(:@after_commit_callback_called) + end + + + def test_delete_behavior_for_plain_models_callbacks + model = CallbackModel.new + model.save + model.remove_called_variables # clear called callback flags + model.delete + assert_equal nil, model.instance_variable_get(:@update_callback_called) assert_equal nil, model.instance_variable_get(:@save_callback_called) + assert_equal nil, model.instance_variable_get(:@validate_called) + assert_equal nil, model.instance_variable_get(:@destroy_callback_called) + assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called) + assert_equal nil, model.instance_variable_get(:@after_commit_callback_called) end def test_destroy_behavior_for_paranoid_models @@ -313,6 +336,15 @@ class CallbackModel < ActiveRecord::Base before_restore {|model| model.instance_variable_set :@restore_callback_called, true } before_update {|model| model.instance_variable_set :@update_callback_called, true } before_save {|model| model.instance_variable_set :@save_callback_called, true} + + after_destroy {|model| model.instance_variable_set :@after_destroy_callback_called, true } + after_commit {|model| model.instance_variable_set :@after_commit_callback_called, true } + + validate {|model| model.instance_variable_set :@validate_called, true } + + def remove_called_variables + instance_variables.each {|name| (name.to_s.end_with?('_called')) ? remove_instance_variable(name) : nil} + end end class ParentModel < ActiveRecord::Base From 6e72b84117155de0cbb697947c378d2d3bc60cdb Mon Sep 17 00:00:00 2001 From: Alex Koppel Date: Mon, 9 Dec 2013 10:39:40 -0600 Subject: [PATCH 35/76] Notify observers of paranoid restores --- lib/paranoia.rb | 7 +++++++ test/paranoia_test.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 0b43af79..e92508e8 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -94,6 +94,13 @@ def self.acts_as_paranoid(options={}) self.paranoia_column = options[:column] || :deleted_at default_scope { where(self.quoted_table_name + ".#{paranoia_column} IS NULL") } + + before_restore { + self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers) + } + after_restore { + self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers) + } end def self.paranoid? diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index f5c187d8..97d67e6b 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -305,6 +305,22 @@ def test_multiple_restore refute c.destroyed? end + def test_observers_notified + a = ParanoidModelWithObservers.create + a.destroy + a.restore! + + assert a.observers_notified.select {|args| args == [:before_restore, a]} + assert a.observers_notified.select {|args| args == [:after_restore, a]} + end + + def test_observers_not_notified_if_not_supported + a = ParanoidModelWithObservers.create + a.destroy + a.restore! + # essentially, we're just ensuring that this doesn't crash + end + private def get_featureful_model FeaturefulModel.new(:name => 'not empty') @@ -378,3 +394,17 @@ class Job < ActiveRecord::Base class CustomColumnModel < ActiveRecord::Base acts_as_paranoid column: :destroyed_at end + +class ParanoidModelWithObservers < ParanoidModel + def observers_notified + @observers_notified ||= [] + end + + def self.notify_observer(*args) + observers_notified << args + end +end + +class ParanoidModelWithoutObservers < ParanoidModel + self.class.send(remove_method :notify_observers) if method_defined?(:notify_observers) +end From 4677f9075152a4d859446a186a6d0b3ae43c02c7 Mon Sep 17 00:00:00 2001 From: "Peter M. Goldstein" Date: Sun, 15 Dec 2013 15:50:48 -0800 Subject: [PATCH 36/76] Update Gemfile and gemspec to allow JRuby and Rubinius. Add JRuby and Rubinius to .travis.yml --- .travis.yml | 4 +++- Gemfile | 9 +++++++++ paranoia.gemspec | 1 - 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 418ef341..47a967f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: ruby rvm: - 1.9.3 - - 2.0.0 \ No newline at end of file + - 2.0.0 + - jruby-19mode + - rbx \ No newline at end of file diff --git a/Gemfile b/Gemfile index d32360e9..5c9b8f0c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,13 @@ source 'https://rubygems.org' +gem 'sqlite3', :platforms => [:ruby] +gem 'activerecord-jdbcsqlite3-adapter', :platforms => [:jruby] + +platforms :rbx do + gem 'rubysl', '~> 2.0' + gem 'rubysl-test-unit' + gem 'rubinius-developer_tools' +end + # Specify your gem's dependencies in paranoia.gemspec gemspec diff --git a/paranoia.gemspec b/paranoia.gemspec index ecc6b81b..44ab6a97 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -17,7 +17,6 @@ Gem::Specification.new do |s| s.add_dependency 'activerecord', '~> 3.2' s.add_development_dependency 'bundler', '>= 1.0.0' - s.add_development_dependency 'sqlite3' s.add_development_dependency 'rake' s.files = `git ls-files`.split("\n") From d7bdb06ec4e7b6d75c1dcdc3ffb3bd8b563518ad Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 18 Dec 2013 09:38:19 +1100 Subject: [PATCH 37/76] Add details about accessing soft-deleted associations --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 258a3fd0..d2a4d63f 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,16 @@ class Client < ActiveRecord::Base end ``` +If you want to access soft-deleted associations, override the getter method: + +```ruby +def product + Product.unscoped { super } +end +``` + +## Acts As Paranoid Migration + You can replace the older acts_as_paranoid methods as follows: | Old Syntax | New Syntax | From a0ec1c937f30b479f8e173b096a9298ed4b29cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Pr=C3=A9vost?= Date: Tue, 17 Dec 2013 09:51:07 -0500 Subject: [PATCH 38/76] Add :recursive option on restore to restore associations Fixes #91 --- lib/paranoia.rb | 31 ++++++++++++++++++++++++++----- test/paranoia_test.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index e92508e8..3d7751a8 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -19,11 +19,11 @@ def only_deleted end alias :deleted :only_deleted - def restore(id) + def restore(id, opts = {}) if id.is_a?(Array) - id.map { |one_id| restore(one_id) } + id.map { |one_id| restore(one_id, opts) } else - only_deleted.find(id).restore! + only_deleted.find(id).restore!(opts) end end end @@ -55,8 +55,13 @@ def delete delete_or_soft_delete end - def restore! - run_callbacks(:restore) { update_column paranoia_column, nil } + def restore!(opts = {}) + ActiveRecord::Base.transaction do + run_callbacks(:restore) do + update_column paranoia_column, nil + restore_associated_records if opts[:recursive] + end + end end alias :restore :restore! @@ -83,6 +88,22 @@ def touch_paranoia_column(with_transaction=false) touch(paranoia_column) end end + + # restore associated records that have been soft deleted when + # we called #destroy + def restore_associated_records + destroyed_associations = self.class.reflect_on_all_associations.select do |association| + association.options[:dependent] == :destroy + end + + destroyed_associations.each do |association| + association = send(association.name) + + if association.paranoid? + association.only_deleted.each { |record| record.restore(:recursive => true) } + end + end + end end class ActiveRecord::Base diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 97d67e6b..712a9ddc 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -18,6 +18,7 @@ ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)' +ActiveRecord::Base.connection.execute 'CREATE TABLE non_paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER)' class ParanoiaTest < Test::Unit::TestCase def test_plain_model_class_is_not_paranoid @@ -305,6 +306,34 @@ def test_multiple_restore refute c.destroyed? end + def test_restore_with_associations + parent = ParentModel.create + first_child = parent.very_related_models.create + second_child = parent.non_paranoid_models.create + + parent.destroy + assert_equal false, parent.deleted_at.nil? + assert_equal false, first_child.reload.deleted_at.nil? + assert_equal true, second_child.destroyed? + + parent.restore! + assert_equal true, parent.deleted_at.nil? + assert_equal false, first_child.reload.deleted_at.nil? + assert_equal true, second_child.destroyed? + + parent.destroy + parent.restore(:recursive => true) + assert_equal true, parent.deleted_at.nil? + assert_equal true, first_child.reload.deleted_at.nil? + assert_equal true, second_child.destroyed? + + parent.destroy + ParentModel.restore(parent.id, :recursive => true) + assert_equal true, parent.reload.deleted_at.nil? + assert_equal true, first_child.reload.deleted_at.nil? + assert_equal true, second_child.destroyed? + end + def test_observers_notified a = ParanoidModelWithObservers.create a.destroy @@ -366,6 +395,8 @@ def remove_called_variables class ParentModel < ActiveRecord::Base acts_as_paranoid has_many :related_models + has_many :very_related_models, :class_name => 'RelatedModel', dependent: :destroy + has_many :non_paranoid_models, dependent: :destroy end class RelatedModel < ActiveRecord::Base @@ -395,6 +426,9 @@ class CustomColumnModel < ActiveRecord::Base acts_as_paranoid column: :destroyed_at end +class NonParanoidModel < ActiveRecord::Base +end + class ParanoidModelWithObservers < ParanoidModel def observers_notified @observers_notified ||= [] From 13397e5599671c3e736d20bac5ae61e96141bc08 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 18 Dec 2013 09:48:54 +1100 Subject: [PATCH 39/76] Update README with more usage examples --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2a4d63f..f8600195 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,22 @@ class Client < ActiveRecord::Base end ``` -Hey presto, it's there! +Hey presto, it's there! Calling `destroy` will now set the `deleted_at` column: + + +``` +>> client.deleted_at => nil +>> client.destroy => client +>> client.deleted_at => [current timestamp] +``` + +If you really want it gone, call `destroy` twice: + +``` +>> client.deleted_at => nil +>> client.destroy => client +>> client.destroy => client +``` If you want a method to be called on destroy, simply provide a _before\_destroy_ callback: @@ -102,6 +117,50 @@ def product end ``` +If you want to find all records, even those which are deleted: + +``` +Client.with_deleted +``` + +If you want to find only the deleted records: + +``` +Client.only_deleted +``` + +If you want to check if a record is soft-deleted: + +``` +client.destroyed? +``` + +If you want to restore a record: + +``` +Client.restore(id) +``` + +If you want to restore a whole bunch of records: + +``` +Client.restore([id1, id2, ..., idN]) +``` + +If you want to restore a record and their dependently destroyed associated records: + +``` +Client.restore(id, :restore => true) +``` + +If you want callbacks to trigger before a restore: + +``` +before_restore :callback_name_goes_here +``` + +For more information, please look at the tests. + ## Acts As Paranoid Migration You can replace the older acts_as_paranoid methods as follows: From ea6c2421502d6507a8dc3a8caa957b81a1404bd4 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 18 Dec 2013 09:49:40 +1100 Subject: [PATCH 40/76] Syntax highlighting makes everything better --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f8600195..74145e5d 100644 --- a/README.md +++ b/README.md @@ -119,43 +119,43 @@ end If you want to find all records, even those which are deleted: -``` +```ruby Client.with_deleted ``` If you want to find only the deleted records: -``` +```ruby Client.only_deleted ``` If you want to check if a record is soft-deleted: -``` +```ruby client.destroyed? ``` If you want to restore a record: -``` +```ruby Client.restore(id) ``` If you want to restore a whole bunch of records: -``` +```ruby Client.restore([id1, id2, ..., idN]) ``` If you want to restore a record and their dependently destroyed associated records: -``` +```ruby Client.restore(id, :restore => true) ``` If you want callbacks to trigger before a restore: -``` +```ruby before_restore :callback_name_goes_here ``` From a789bd9ec480982bf3666feceaca589f2949e53f Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 18 Dec 2013 13:16:33 +1100 Subject: [PATCH 41/76] It's :recursive, not :restore --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74145e5d..d76e1b00 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Client.restore([id1, id2, ..., idN]) If you want to restore a record and their dependently destroyed associated records: ```ruby -Client.restore(id, :restore => true) +Client.restore(id, :recursive => true) ``` If you want callbacks to trigger before a restore: From 113c8f4c9e12d299ed4615fb267e39def0c20d96 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 3 Jan 2014 16:42:15 +1100 Subject: [PATCH 42/76] Revert behaviour of calling destroy twice deleting records As per #92 Conflicts: lib/paranoia.rb --- lib/paranoia.rb | 12 ++++-------- test/paranoia_test.rb | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 3d7751a8..716faeec 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -47,12 +47,12 @@ def self.extended(klazz) end def destroy - run_callbacks(:destroy) { delete_or_soft_delete(true) } + run_callbacks(:destroy) { touch_paranoia_column(true) } end def delete return if new_record? - delete_or_soft_delete + touch_paranoia_column(false) end def restore!(opts = {}) @@ -72,11 +72,6 @@ def destroyed? alias :deleted? :destroyed? private - # select and exec delete or soft-delete. - # @param with_transaction [Boolean] exec with ActiveRecord Transactions, when soft-delete. - def delete_or_soft_delete(with_transaction=false) - destroyed? ? destroy! : touch_paranoia_column(with_transaction) - end # touch paranoia column. # insert time to paranoia column. @@ -108,7 +103,8 @@ def restore_associated_records class ActiveRecord::Base def self.acts_as_paranoid(options={}) - alias :destroy! :destroy + alias :ar_destroy :destroy + alias :destroy! :ar_destroy alias :delete! :delete include Paranoia class_attribute :paranoia_column diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 712a9ddc..4bfa59b6 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -240,13 +240,14 @@ def test_restore assert_equal false, model.destroyed? end + # Regression test for #92 def test_destroy_twice model = ParanoidModel.new model.save model.destroy model.destroy - assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count + assert_equal 1, ParanoidModel.unscoped.where(id: model.id).count end def test_restore_behavior_for_callbacks From 9680034d5d29103a03318cc827432c19159bdf75 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 3 Jan 2014 16:56:23 +1100 Subject: [PATCH 43/76] update README for #92 --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d76e1b00..de371482 100644 --- a/README.md +++ b/README.md @@ -75,13 +75,11 @@ Hey presto, it's there! Calling `destroy` will now set the `deleted_at` column: >> client.deleted_at => [current timestamp] ``` -If you really want it gone, call `destroy` twice: +If you really want it gone *gone*, call `destroy!` ``` >> client.deleted_at => nil ->> client.destroy => client ->> client.destroy => client -``` +>> client.destroy! => client If you want a method to be called on destroy, simply provide a _before\_destroy_ callback: From 2fc1a01bed98ceb034e5ddc949e39c9e5325e659 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 3 Jan 2014 16:57:19 +1100 Subject: [PATCH 44/76] Support Ruby 2.1.0 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 47a967f1..f0efff6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,6 @@ language: ruby rvm: - 1.9.3 - 2.0.0 + - 2.1.0 - jruby-19mode - rbx \ No newline at end of file From b5df36e5ff4d69c27034f24341b03635909ea65f Mon Sep 17 00:00:00 2001 From: Geoffrey Roguelon Date: Fri, 3 Jan 2014 21:06:41 +0100 Subject: [PATCH 45/76] Fix some syntax errors in README.md [ci skip] --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index de371482..1e387548 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,9 @@ If you really want it gone *gone*, call `destroy!` ``` >> client.deleted_at => nil >> client.destroy! => client +``` -If you want a method to be called on destroy, simply provide a _before\_destroy_ callback: +If you want a method to be called on destroy, simply provide a `before_destroy` callback: ```ruby class Client < ActiveRecord::Base @@ -97,7 +98,7 @@ class Client < ActiveRecord::Base end ``` -If you want to use a column other than deleted_at, you can pass it as an option: +If you want to use a column other than `deleted_at`, you can pass it as an option: ```ruby class Client < ActiveRecord::Base From 2ce98a2a85d817bb190344de08c2db27f212883f Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 10 Jan 2014 10:55:25 +1030 Subject: [PATCH 46/76] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1e387548..785c3964 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ You would use either plugin / gem if you wished that when you called `destroy` o If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object. +If a record has has_many associations defined AND those associations have dependent: :destroy set on them, then they will also be soft-deleted. If they don't have that, then they will not be deleted. + ## Installation & Usage For Rails 3, please use version 1 of Paranoia: From ce770898d0ff6bbd7072f444dddd530b0757409a Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 10 Jan 2014 10:56:35 +1030 Subject: [PATCH 47/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 785c3964..ddd6bcd9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ You would use either plugin / gem if you wished that when you called `destroy` o If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object. -If a record has has_many associations defined AND those associations have dependent: :destroy set on them, then they will also be soft-deleted. If they don't have that, then they will not be deleted. +If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted. If they don't have that, then they will not be deleted. ## Installation & Usage From d6653bb4fb5ace93d69fabf174fd1111816e86b6 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 17 Jan 2014 06:53:25 +1100 Subject: [PATCH 48/76] Bump to 1.3.3 --- lib/paranoia/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index 8a4ae1d2..c96ce00b 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = '1.3.2' + VERSION = '1.3.3' end From d2ac6586b290165134aba7af643df9d719a1cbda Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 15 Jan 2014 16:52:11 +1030 Subject: [PATCH 49/76] Update README.md, clarifying behaviour of acts_as_paranoid call. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddd6bcd9..47b5f2e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ You would use either plugin / gem if you wished that when you called `destroy` o If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object. -If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted. If they don't have that, then they will not be deleted. +If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if ``acts_as_paranoid`` is set, otherwise the normal destroy will be called. ## Installation & Usage From 79da3a4dda4065bf2dac50f1689e1aaaef56bf95 Mon Sep 17 00:00:00 2001 From: Vincent Bonmalais Date: Sat, 25 Jan 2014 15:33:29 +0000 Subject: [PATCH 50/76] Fix destroy return value. Rails normally returns the model on successful destroy and false if any callback fails. Fixes #104 --- lib/paranoia.rb | 3 ++- test/paranoia_test.rb | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 716faeec..b11a649b 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -47,7 +47,8 @@ def self.extended(klazz) end def destroy - run_callbacks(:destroy) { touch_paranoia_column(true) } + callbacks_result = run_callbacks(:destroy) { touch_paranoia_column(true) } + callbacks_result ? self : false end def delete diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 4bfa59b6..778f0f23 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -13,6 +13,7 @@ ActiveRecord::Base.connection.execute 'CREATE TABLE featureful_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(32))' ActiveRecord::Base.connection.execute 'CREATE TABLE plain_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' +ActiveRecord::Base.connection.execute 'CREATE TABLE fail_callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER NOT NULL, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' @@ -250,6 +251,20 @@ def test_destroy_twice assert_equal 1, ParanoidModel.unscoped.where(id: model.id).count end + def test_destroy_return_value_on_success + model = ParanoidModel.create + return_value = model.destroy + + assert_equal(return_value, model) + end + + def test_destroy_return_value_on_failure + model = FailCallbackModel.create + return_value = model.destroy + + assert_equal(return_value, false) + end + def test_restore_behavior_for_callbacks model = CallbackModel.new model.save @@ -368,6 +383,13 @@ class ParanoidModel < ActiveRecord::Base acts_as_paranoid end +class FailCallbackModel < ActiveRecord::Base + belongs_to :parent_model + acts_as_paranoid + + before_destroy { |_| false } +end + class FeaturefulModel < ActiveRecord::Base acts_as_paranoid validates :name, :presence => true, :uniqueness => true From 4a41dda7afa7e35ef2a1b88cfff85f6a40dde71c Mon Sep 17 00:00:00 2001 From: David Piegza Date: Sat, 1 Feb 2014 18:36:42 +0100 Subject: [PATCH 51/76] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47b5f2e6..be9fdf25 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/techno You would use either plugin / gem if you wished that when you called `destroy` on an Active Record object that it didn't actually destroy it, but just "hid" the record. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field. -If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object. +If you wish to actually destroy an object you may call destroy! on it. If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if ``acts_as_paranoid`` is set, otherwise the normal destroy will be called. From e0f693d0ca4207377c3b29591aa52319f41929f2 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 6 Feb 2014 07:13:31 +0000 Subject: [PATCH 52/76] Use really_destroy! for really destroying things --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be9fdf25..e1d51875 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/techno You would use either plugin / gem if you wished that when you called `destroy` on an Active Record object that it didn't actually destroy it, but just "hid" the record. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field. -If you wish to actually destroy an object you may call destroy! on it. +If you wish to actually destroy an object you may call `really_destroy!`. If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if ``acts_as_paranoid`` is set, otherwise the normal destroy will be called. From dfd6eb08a8f8122d6c1a7b35e16b2f0ea0fea8d7 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 6 Feb 2014 07:16:41 +0000 Subject: [PATCH 53/76] Use rails3 branch instead of 'master' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1d51875..bdbffa99 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ gem 'paranoia', '~> 2.0' Of course you can install this from GitHub as well: ```ruby -gem 'paranoia', :github => 'radar/paranoia', :branch => 'master' +gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails3' # or gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails4' ``` From 8151fc898f50bb1a8e4b5a34d95387b555c8b042 Mon Sep 17 00:00:00 2001 From: Rene van Lieshout Date: Thu, 13 Feb 2014 11:33:31 +0100 Subject: [PATCH 54/76] Add index to deleted_at column in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bdbffa99..5b688c4a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Updating is as simple as `bundle update paranoia`. Run: ```shell -rails generate migration AddDeletedAtToClients deleted_at:datetime +rails generate migration AddDeletedAtToClients deleted_at:datetime:index ``` and now you have a migration @@ -52,6 +52,7 @@ and now you have a migration class AddDeletedAtToClients < ActiveRecord::Migration def change add_column :clients, :deleted_at, :datetime + add_index :clients, :deleted_at end end ``` From acbc916c10515234793f624679b63ad03f01fab9 Mon Sep 17 00:00:00 2001 From: Dave Mayo Date: Fri, 7 Mar 2014 00:40:45 -0500 Subject: [PATCH 55/76] Regression tests and full fix for #118 * Based on partial fix by @stupergenius * Handles nil case for hasOne associations --- lib/paranoia.rb | 14 ++++++++++---- test/paranoia_test.rb | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index b11a649b..199488f7 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -93,10 +93,16 @@ def restore_associated_records end destroyed_associations.each do |association| - association = send(association.name) - - if association.paranoid? - association.only_deleted.each { |record| record.restore(:recursive => true) } + association_data = send(association.name) + + unless association_data.nil? + if association_data.paranoid? + if association.collection? + association_data.only_deleted.each { |record| record.restore(:recursive => true) } + else + association_data.restore(:recursive => true) + end + end end end end diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 778f0f23..9ee9b3b0 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -10,6 +10,7 @@ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => DB_FILE ActiveRecord::Base.connection.execute 'CREATE TABLE parent_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)' +ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, paranoid_model_with_has_one_id INTEGER)' ActiveRecord::Base.connection.execute 'CREATE TABLE featureful_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(32))' ActiveRecord::Base.connection.execute 'CREATE TABLE plain_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' @@ -350,6 +351,40 @@ def test_restore_with_associations assert_equal true, second_child.destroyed? end + # regression tests for #118 + def test_restore_with_has_one_association + # setup and destroy test objects + hasOne = ParanoidModelWithHasOne.create + belongsTo = ParanoidModelWithBelong.create + hasOne.paranoid_model_with_belong = belongsTo + hasOne.save! + + hasOne.destroy + assert_equal false, hasOne.deleted_at.nil? + assert_equal false, belongsTo.deleted_at.nil? + + # Does it restore has_one associations? + hasOne.restore(:recursive => true) + hasOne.save! + + assert_equal true, hasOne.reload.deleted_at.nil? + assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}" + assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record" + end + + def test_restore_with_nil_has_one_association + # setup and destroy test object + hasOne = ParanoidModelWithHasOne.create + hasOne.destroy + assert_equal false, hasOne.reload.deleted_at.nil? + + # Does it raise NoMethodException on restore of nil + assert_nothing_raised do + hasOne.restore(:recursive => true) + end + assert hasOne.reload.deleted_at.nil? + end + def test_observers_notified a = ParanoidModelWithObservers.create a.destroy @@ -465,3 +500,13 @@ def self.notify_observer(*args) class ParanoidModelWithoutObservers < ParanoidModel self.class.send(remove_method :notify_observers) if method_defined?(:notify_observers) end + +# refer back to regression test for #118 +class ParanoidModelWithHasOne < ParanoidModel + has_one :paranoid_model_with_belong, :dependent => :destroy +end + +class ParanoidModelWithBelong < ActiveRecord::Base + acts_as_paranoid + belongs_to :paranoid_model_with_has_one +end From c8cfa18c56db69e72db25a6dcfb62d7a6a1bdada Mon Sep 17 00:00:00 2001 From: Andrew Slotin Date: Mon, 17 Mar 2014 19:09:56 +0100 Subject: [PATCH 56/76] Explicitly load AR unless already loaded Fixes #125 --- lib/paranoia.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 199488f7..d283caa8 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -1,3 +1,5 @@ +require 'active_record' unless defined? ActiveRecord + module Paranoia def self.included(klazz) klazz.extend Query From 2301d5efc2a470eaa764ebaaf5e5c425682788af Mon Sep 17 00:00:00 2001 From: Matt Connolly Date: Tue, 18 Mar 2014 16:06:08 +1000 Subject: [PATCH 57/76] Add rspec matcher: act_as_paranoid Fixes #126 --- lib/paranoia.rb | 2 ++ lib/paranoia/rspec.rb | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100644 lib/paranoia/rspec.rb diff --git a/lib/paranoia.rb b/lib/paranoia.rb index d283caa8..ae8eff8b 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -150,3 +150,5 @@ def paranoia_column self.class.paranoia_column end end + +require 'paranoia/rspec' if defined? RSpec diff --git a/lib/paranoia/rspec.rb b/lib/paranoia/rspec.rb new file mode 100644 index 00000000..7eef1e09 --- /dev/null +++ b/lib/paranoia/rspec.rb @@ -0,0 +1,9 @@ +require 'rspec/expectations' + +# Validate the subject's class did call "acts_as_paranoid" +RSpec::Matchers.define :act_as_paranoid do + match { |subject| subject.class.ancestors.include?(Paranoia) } + + failure_message_for_should { "#{subject.class} should use `acts_as_paranoid`" } + failure_message_for_should_not { "#{subject.class} should not use `acts_as_paranoid`" } +end From bbfac32e373abe382b4f4ab1779bff066ba63af6 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 12:29:42 +1000 Subject: [PATCH 58/76] really_destroy! will now really destroy dependent = :destroy associated records Fixes #117 Conflicts: lib/paranoia.rb test/paranoia_test.rb --- lib/paranoia.rb | 25 +++++++++++++++++++++---- test/paranoia_test.rb | 9 ++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index ae8eff8b..656d72b3 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -80,10 +80,15 @@ def destroyed? # insert time to paranoia column. # @param with_transaction [Boolean] exec with ActiveRecord Transactions. def touch_paranoia_column(with_transaction=false) - if with_transaction - with_transaction_returning_status { touch(paranoia_column) } - else - touch(paranoia_column) + # This method is (potentially) called from really_destroy + # The object the method is being called on may be frozen + # Let's not touch it if it's frozen. + unless self.frozen? + if with_transaction + with_transaction_returning_status { touch(paranoia_column) } + else + touch(paranoia_column) + end end end @@ -115,6 +120,18 @@ def self.acts_as_paranoid(options={}) alias :ar_destroy :destroy alias :destroy! :ar_destroy alias :delete! :delete + def really_destroy! + dependent_reflections = self.reflections.select do |name, reflection| + reflection.options[:dependent] == :destroy + end + if dependent_reflections.any? + dependent_reflections.each do |name, _| + self.send(name).each(&:really_destroy!) + end + end + destroy! + end + include Paranoia class_attribute :paranoia_column diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 9ee9b3b0..1280efda 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -281,7 +281,7 @@ def test_restore_behavior_for_callbacks assert model.instance_variable_get(:@restore_callback_called) end - def test_real_destroy + def test_really_destroy model = ParanoidModel.new model.save model.destroy! @@ -289,6 +289,13 @@ def test_real_destroy assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end + def test_real_destroy_dependent_destroy + parent = ParentModel.create + child = parent.very_related_models.create + parent.really_destroy! + refute RelatedModel.unscoped.exists?(child.id) + end + def test_real_delete model = ParanoidModel.new model.save From d25cae9f03cc9a1bcbef14333087443c332bbdcb Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 13:53:13 +1000 Subject: [PATCH 59/76] Use an unscoped call on associations within really_destroy to REALLY DESTROY ALL THE THINGS :bomb: Conflicts: test/paranoia_test.rb --- lib/paranoia.rb | 2 +- test/paranoia_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 656d72b3..c35ea7cc 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -126,7 +126,7 @@ def really_destroy! end if dependent_reflections.any? dependent_reflections.each do |name, _| - self.send(name).each(&:really_destroy!) + self.send(name).unscoped.each(&:really_destroy!) end end destroy! diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 1280efda..db7f9950 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -296,6 +296,14 @@ def test_real_destroy_dependent_destroy refute RelatedModel.unscoped.exists?(child.id) end + def test_real_destroy_dependent_destroy_after_normal_destroy + parent = ParentModel.create + child = parent.very_related_models.create + parent.destroy + parent.really_destroy! + refute RelatedModel.unscoped.exists?(child.id) + end + def test_real_delete model = ParanoidModel.new model.save From 07028f1e1d3241426a804c6568e51743f5243f75 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 14:05:33 +1000 Subject: [PATCH 60/76] Revert "Use an unscoped call on associations within really_destroy to REALLY DESTROY ALL THE THINGS" This reverts commit d25cae9f03cc9a1bcbef14333087443c332bbdcb. This removes the association id --- lib/paranoia.rb | 2 +- test/paranoia_test.rb | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index c35ea7cc..656d72b3 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -126,7 +126,7 @@ def really_destroy! end if dependent_reflections.any? dependent_reflections.each do |name, _| - self.send(name).unscoped.each(&:really_destroy!) + self.send(name).each(&:really_destroy!) end end destroy! diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index db7f9950..1280efda 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -296,14 +296,6 @@ def test_real_destroy_dependent_destroy refute RelatedModel.unscoped.exists?(child.id) end - def test_real_destroy_dependent_destroy_after_normal_destroy - parent = ParentModel.create - child = parent.very_related_models.create - parent.destroy - parent.really_destroy! - refute RelatedModel.unscoped.exists?(child.id) - end - def test_real_delete model = ParanoidModel.new model.save From 7e9b1e8128d58afb212b9ac66096c4b27582e365 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 14:06:01 +1000 Subject: [PATCH 61/76] Revert "Revert "Use an unscoped call on associations within really_destroy to REALLY DESTROY ALL THE THINGS"" This reverts commit 07028f1e1d3241426a804c6568e51743f5243f75. --- lib/paranoia.rb | 2 +- test/paranoia_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 656d72b3..c35ea7cc 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -126,7 +126,7 @@ def really_destroy! end if dependent_reflections.any? dependent_reflections.each do |name, _| - self.send(name).each(&:really_destroy!) + self.send(name).unscoped.each(&:really_destroy!) end end destroy! diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 1280efda..db7f9950 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -296,6 +296,14 @@ def test_real_destroy_dependent_destroy refute RelatedModel.unscoped.exists?(child.id) end + def test_real_destroy_dependent_destroy_after_normal_destroy + parent = ParentModel.create + child = parent.very_related_models.create + parent.destroy + parent.really_destroy! + refute RelatedModel.unscoped.exists?(child.id) + end + def test_real_delete model = ParanoidModel.new model.save From 9a305b26847c0d4b7457358f187c98213f0b6927 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 14:12:07 +1000 Subject: [PATCH 62/76] Really only destroy associated records Conflicts: test/paranoia_test.rb --- lib/paranoia.rb | 5 ++++- test/paranoia_test.rb | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index c35ea7cc..75e4074a 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -126,7 +126,10 @@ def really_destroy! end if dependent_reflections.any? dependent_reflections.each do |name, _| - self.send(name).unscoped.each(&:really_destroy!) + associated_records = self.send(name) + # Paranoid models will have this method, non-paranoid models will not + associated_records = associated_records.with_deleted if associated_records.respond_to?(:with_deleted) + associated_records.each(&:really_destroy!) end end destroy! diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index db7f9950..b7c98034 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -303,7 +303,18 @@ def test_real_destroy_dependent_destroy_after_normal_destroy parent.really_destroy! refute RelatedModel.unscoped.exists?(child.id) end - + + def test_real_destroy_dependent_destroy_after_normal_destroy_does_not_delete_other_children + parent_1 = ParentModel.create + child_1 = parent_1.very_related_models.create + + parent_2 = ParentModel.create + child_2 = parent_2.very_related_models.create + parent_1.destroy + parent_1.really_destroy! + assert RelatedModel.unscoped.exists?(child_2.id) + end + def test_real_delete model = ParanoidModel.new model.save From 72bc7c01c22691d49b9d58c2bf30ce745d2a2dc9 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 8 Apr 2014 14:18:48 +1000 Subject: [PATCH 63/76] Mention latest changes to really_destroy in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b688c4a..d44ce9c1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/techno You would use either plugin / gem if you wished that when you called `destroy` on an Active Record object that it didn't actually destroy it, but just "hid" the record. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field. -If you wish to actually destroy an object you may call `really_destroy!`. +If you wish to actually destroy an object you may call `really_destroy!`. **WARNING**: This will also *really destroy* all `dependent: destroy` records, so please aim this method away from face when using.** If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if ``acts_as_paranoid`` is set, otherwise the normal destroy will be called. From dabef4b7297952717efba4fe6e967190fa326a7e Mon Sep 17 00:00:00 2001 From: yratanov Date: Fri, 11 Apr 2014 18:30:38 +0800 Subject: [PATCH 64/76] Adds #really_destroyed? method as a fallback to default rails #destroyed? --- lib/paranoia.rb | 1 + test/paranoia_test.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 75e4074a..3307267c 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -117,6 +117,7 @@ def restore_associated_records class ActiveRecord::Base def self.acts_as_paranoid(options={}) + alias :really_destroyed? :destroyed? alias :ar_destroy :destroy alias :destroy! :ar_destroy alias :delete! :delete diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index b7c98034..4dcc1b9d 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -102,6 +102,7 @@ def test_destroy_behavior_for_paranoid_models model.destroy assert_equal false, model.deleted_at.nil? + assert_equal false, model.really_destroyed? assert_equal 0, model.class.count assert_equal 1, model.class.unscoped.count @@ -289,6 +290,14 @@ def test_really_destroy assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count end + def test_really_destroyed + model = ParanoidModel.new + model.save + model.destroy! + + assert model.really_destroyed? + end + def test_real_destroy_dependent_destroy parent = ParentModel.create child = parent.very_related_models.create From ad3e711c5ec749ccb000bb160d35f41448bd04c8 Mon Sep 17 00:00:00 2001 From: Matthew Jording Date: Thu, 12 Jun 2014 20:24:32 -0400 Subject: [PATCH 65/76] Update paranoia_test.rb Removed deprecated assert_nothing_raised from test. Fixes #136 --- test/paranoia_test.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 4dcc1b9d..83457fa9 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -414,9 +414,8 @@ def test_restore_with_nil_has_one_association assert_equal false, hasOne.reload.deleted_at.nil? # Does it raise NoMethodException on restore of nil - assert_nothing_raised do - hasOne.restore(:recursive => true) - end + hasOne.restore(:recursive => true) + assert hasOne.reload.deleted_at.nil? end From b1ec37eb2c0e0a835f5af4984306b94ef617bd70 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 23 Jun 2014 16:36:04 -0700 Subject: [PATCH 66/76] Rails 3.1 is fine Fixes #139 --- paranoia.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paranoia.gemspec b/paranoia.gemspec index 44ab6a97..b78d8377 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = '>= 1.3.6' s.rubyforge_project = 'paranoia' - s.add_dependency 'activerecord', '~> 3.2' + s.add_dependency 'activerecord', '~> 3.1' s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'rake' From 48c6c9078885ed488370974bc91982b0999b62ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20S=C3=A5gfors?= Date: Tue, 12 Aug 2014 14:24:37 +0300 Subject: [PATCH 67/76] Replace deprecated RSpec failure_message_for_should syntax --- lib/paranoia/rspec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia/rspec.rb b/lib/paranoia/rspec.rb index 7eef1e09..b6dc07b5 100644 --- a/lib/paranoia/rspec.rb +++ b/lib/paranoia/rspec.rb @@ -4,6 +4,6 @@ RSpec::Matchers.define :act_as_paranoid do match { |subject| subject.class.ancestors.include?(Paranoia) } - failure_message_for_should { "#{subject.class} should use `acts_as_paranoid`" } - failure_message_for_should_not { "#{subject.class} should not use `acts_as_paranoid`" } + failure_message { "#{subject.class} should use `acts_as_paranoid`" } + failure_message_when_negated { "#{subject.class} should not use `acts_as_paranoid`" } end From 08e37436a46ea479f1136ad9db337413fc97e7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20S=C3=A5gfors?= Date: Tue, 12 Aug 2014 14:31:34 +0300 Subject: [PATCH 68/76] Rephrase matcher failure messages according to RSpec 3 style --- lib/paranoia/rspec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/paranoia/rspec.rb b/lib/paranoia/rspec.rb index b6dc07b5..9bbbc983 100644 --- a/lib/paranoia/rspec.rb +++ b/lib/paranoia/rspec.rb @@ -4,6 +4,6 @@ RSpec::Matchers.define :act_as_paranoid do match { |subject| subject.class.ancestors.include?(Paranoia) } - failure_message { "#{subject.class} should use `acts_as_paranoid`" } - failure_message_when_negated { "#{subject.class} should not use `acts_as_paranoid`" } + failure_message { "expected #{subject.class} to use `acts_as_paranoid`" } + failure_message_when_negated { "expected #{subject.class} not to use `acts_as_paranoid`" } end From 16027f72bb3ab30233f065849024c483a8f9d25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20S=C3=A5gfors?= Date: Tue, 12 Aug 2014 14:33:06 +0300 Subject: [PATCH 69/76] Retain compatibility with RSpec 2 matchers Fixes #156 --- lib/paranoia/rspec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/paranoia/rspec.rb b/lib/paranoia/rspec.rb index 9bbbc983..578de759 100644 --- a/lib/paranoia/rspec.rb +++ b/lib/paranoia/rspec.rb @@ -6,4 +6,8 @@ failure_message { "expected #{subject.class} to use `acts_as_paranoid`" } failure_message_when_negated { "expected #{subject.class} not to use `acts_as_paranoid`" } + + # RSpec 2 compatibility: + alias_method :failure_message_for_should, :failure_message + alias_method :failure_message_for_should_not, :failure_message_when_negated end From 68794fdb9a8f8079c34f83f4fd6b345c7f3c7a00 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 20 Jan 2015 08:28:28 +1100 Subject: [PATCH 70/76] Bump to 1.3.4 --- lib/paranoia/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paranoia/version.rb b/lib/paranoia/version.rb index c96ce00b..5d02e025 100644 --- a/lib/paranoia/version.rb +++ b/lib/paranoia/version.rb @@ -1,3 +1,3 @@ module Paranoia - VERSION = '1.3.3' + VERSION = '1.3.4' end From 11171c757040999466d1a57c43ae27cfa356b272 Mon Sep 17 00:00:00 2001 From: Sammy Larbi Date: Wed, 12 Mar 2014 07:12:42 -0500 Subject: [PATCH 71/76] add failing test that shows Paranoia not working with validates_uniquness_of --- test/paranoia_test.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 83457fa9..62b3a975 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -16,7 +16,7 @@ ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE fail_callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER NOT NULL, deleted_at DATETIME)' -ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' +ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(10))' ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)' ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)' @@ -434,6 +434,13 @@ def test_observers_not_notified_if_not_supported a.restore! # essentially, we're just ensuring that this doesn't crash end + + def test_validates_uniqueness_only_checks_non_deleted_records + a = Employer.create!(name: "A") + a.destroy + b = Employer.new(name: "A") + assert b.valid? + end private def get_featureful_model @@ -498,6 +505,7 @@ class RelatedModel < ActiveRecord::Base class Employer < ActiveRecord::Base acts_as_paranoid + validates_uniqueness_of :name has_many :jobs has_many :employees, :through => :jobs end From 971a6cd10c400b66d33703936b418f1877a4cae5 Mon Sep 17 00:00:00 2001 From: Sammy Larbi Date: Sun, 16 Mar 2014 09:08:12 -0500 Subject: [PATCH 72/76] Ensure uniqueness validation is scoped by deleted_at Fixes #114 --- lib/paranoia.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 3307267c..56425daf 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -172,4 +172,18 @@ def paranoia_column end end + require 'paranoia/rspec' if defined? RSpec + +module ActiveRecord + module Validations + class UniquenessValidator < ActiveModel::EachValidator + protected + def build_relation_with_paranoia(klass, table, attribute, value) + relation = build_relation_without_paranoia(klass, table, attribute, value) + relation.and(klass.quoted_table_name + ".#{klass.paranoia_column} IS NULL") + end + alias_method_chain :build_relation, :paranoia + end + end +end From 4a4497ec1591af0346942673204b37b408fbd28f Mon Sep 17 00:00:00 2001 From: Sammy Larbi Date: Thu, 18 Jun 2015 05:45:11 -0500 Subject: [PATCH 73/76] Add test/unit as a dev dependency So the project can run tests after `bundle install` --- paranoia.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/paranoia.gemspec b/paranoia.gemspec index b78d8377..8ec10e66 100644 --- a/paranoia.gemspec +++ b/paranoia.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler', '>= 1.0.0' s.add_development_dependency 'rake' + s.add_development_dependency 'test-unit' s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map { |f| f =~ /^bin\/(.*)/ ? $1 : nil }.compact From 48a7137ff812bea29b9f7fcad6e0ef11b99266d5 Mon Sep 17 00:00:00 2001 From: Sammy Larbi Date: Sun, 21 Jun 2015 17:58:01 -0500 Subject: [PATCH 74/76] Fix "normal" validates_uniqueness_of after #122 broke it --- lib/paranoia.rb | 2 +- test/paranoia_test.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 56425daf..e84ae18d 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -181,7 +181,7 @@ class UniquenessValidator < ActiveModel::EachValidator protected def build_relation_with_paranoia(klass, table, attribute, value) relation = build_relation_without_paranoia(klass, table, attribute, value) - relation.and(klass.quoted_table_name + ".#{klass.paranoia_column} IS NULL") + relation.and(klass.arel_table[klass.paranoia_column].eq(nil)) end alias_method_chain :build_relation, :paranoia end diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 62b3a975..9ed05107 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -415,7 +415,7 @@ def test_restore_with_nil_has_one_association # Does it raise NoMethodException on restore of nil hasOne.restore(:recursive => true) - + assert hasOne.reload.deleted_at.nil? end @@ -434,7 +434,7 @@ def test_observers_not_notified_if_not_supported a.restore! # essentially, we're just ensuring that this doesn't crash end - + def test_validates_uniqueness_only_checks_non_deleted_records a = Employer.create!(name: "A") a.destroy @@ -442,6 +442,12 @@ def test_validates_uniqueness_only_checks_non_deleted_records assert b.valid? end + def test_validates_uniqueness_still_works_on_non_deleted_records + a = Employer.create!(name: "A") + b = Employer.new(name: "A") + refute b.valid? + end + private def get_featureful_model FeaturefulModel.new(:name => 'not empty') From fa68929fd01af2b056a2863b35d9f0354b904578 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Mon, 9 Nov 2015 14:25:42 -0800 Subject: [PATCH 75/76] Fix restore to be scoped and add callbacks for real destroys --- lib/paranoia.rb | 53 ++++++++++++++++++++++++++----------------- test/paranoia_test.rb | 24 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index e84ae18d..ee8cf4ae 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -32,18 +32,20 @@ def restore(id, opts = {}) module Callbacks def self.extended(klazz) - klazz.define_callbacks :restore + [:restore, :really_destroy].each do |callback_name| + klazz.define_callbacks callback_name - klazz.define_singleton_method("before_restore") do |*args, &block| - set_callback(:restore, :before, *args, &block) - end + klazz.define_singleton_method("before_#{callback_name}") do |*args, &block| + set_callback(callback_name, :before, *args, &block) + end - klazz.define_singleton_method("around_restore") do |*args, &block| - set_callback(:restore, :around, *args, &block) - end + klazz.define_singleton_method("around_#{callback_name}") do |*args, &block| + set_callback(callback_name, :around, *args, &block) + end - klazz.define_singleton_method("after_restore") do |*args, &block| - set_callback(:restore, :after, *args, &block) + klazz.define_singleton_method("after_#{callback_name}") do |*args, &block| + set_callback(callback_name, :after, *args, &block) + end end end end @@ -61,8 +63,10 @@ def delete def restore!(opts = {}) ActiveRecord::Base.transaction do run_callbacks(:restore) do - update_column paranoia_column, nil - restore_associated_records if opts[:recursive] + self.class.unscoped do + update_column paranoia_column, nil + restore_associated_records if opts[:recursive] + end end end end @@ -122,18 +126,23 @@ def self.acts_as_paranoid(options={}) alias :destroy! :ar_destroy alias :delete! :delete def really_destroy! - dependent_reflections = self.reflections.select do |name, reflection| - reflection.options[:dependent] == :destroy - end - if dependent_reflections.any? - dependent_reflections.each do |name, _| - associated_records = self.send(name) - # Paranoid models will have this method, non-paranoid models will not - associated_records = associated_records.with_deleted if associated_records.respond_to?(:with_deleted) - associated_records.each(&:really_destroy!) + transaction do + run_callbacks(:really_destroy) do + dependent_reflections = self.reflections.select do |name, reflection| + reflection.options[:dependent] == :destroy + end + if dependent_reflections.any? + dependent_reflections.each do |name, _| + associated_records = self.send(name) + # Paranoid models will have this method, non-paranoid models will not + next unless associated_records && associated_records.paranoid? + associated_records = associated_records.with_deleted if associated_records.respond_to?(:with_deleted) + associated_records.each(&:really_destroy!) + end + end + destroy! end end - destroy! end include Paranoia @@ -187,3 +196,5 @@ def build_relation_with_paranoia(klass, table, attribute, value) end end end + +ActiveRecord::Callbacks::CALLBACKS.push(:before_restore, :after_restore, :before_really_destroy, :after_really_destroy) diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 9ed05107..6dae9a7e 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -73,6 +73,9 @@ def test_destroy_behavior_for_plain_models_callbacks assert_equal nil, model.instance_variable_get(:@update_callback_called) assert_equal nil, model.instance_variable_get(:@save_callback_called) assert_equal nil, model.instance_variable_get(:@validate_called) + assert_equal nil, model.instance_variable_get(:@before_really_destroy_called) + assert_equal nil, model.instance_variable_get(:@really_destroy_called) + assert_equal nil, model.instance_variable_get(:@after_really_destroy_called) assert model.instance_variable_get(:@destroy_callback_called) assert model.instance_variable_get(:@after_destroy_callback_called) @@ -92,6 +95,8 @@ def test_delete_behavior_for_plain_models_callbacks assert_equal nil, model.instance_variable_get(:@destroy_callback_called) assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called) assert_equal nil, model.instance_variable_get(:@after_commit_callback_called) + assert_equal nil, model.instance_variable_get(:@after_really_destroy_called) + assert_equal nil, model.instance_variable_get(:@really_destroy_called) end def test_destroy_behavior_for_paranoid_models @@ -298,6 +303,22 @@ def test_really_destroyed assert model.really_destroyed? end + def test_really_destroy_with_callback + model = CallbackModel.new + model.save + model.remove_called_variables + + model.really_destroy! + + assert model.instance_variable_get(:@destroy_callback_called) + assert model.instance_variable_get(:@after_destroy_callback_called) + + assert model.instance_variable_get(:@really_destroy_called) + assert model.instance_variable_get(:@after_really_destroy_called) + + refute CallbackModel.unscoped.exists?(model.id) + end + def test_real_destroy_dependent_destroy parent = ParentModel.create child = parent.very_related_models.create @@ -492,6 +513,9 @@ class CallbackModel < ActiveRecord::Base validate {|model| model.instance_variable_set :@validate_called, true } + before_really_destroy { |model| model.instance_variable_set :@really_destroy_called, true } + after_really_destroy { |model| model.instance_variable_set :@after_really_destroy_called, true } + def remove_called_variables instance_variables.each {|name| (name.to_s.end_with?('_called')) ? remove_instance_variable(name) : nil} end From 0ba5ff01c54700863d3c458aa41febed690fe819 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Mon, 9 Nov 2015 16:46:09 -0800 Subject: [PATCH 76/76] Fix for collections and if relation isnt paranoid --- lib/paranoia.rb | 10 +++++++--- test/paranoia_test.rb | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/paranoia.rb b/lib/paranoia.rb index ee8cf4ae..d64d8774 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -132,12 +132,15 @@ def really_destroy! reflection.options[:dependent] == :destroy end if dependent_reflections.any? - dependent_reflections.each do |name, _| + dependent_reflections.each do |name, reflection| associated_records = self.send(name) # Paranoid models will have this method, non-paranoid models will not next unless associated_records && associated_records.paranoid? - associated_records = associated_records.with_deleted if associated_records.respond_to?(:with_deleted) - associated_records.each(&:really_destroy!) + if reflection.collection? + associated_records.with_deleted.each(&:really_destroy!) + next + end + associated_records.really_destroy! end end destroy! @@ -190,6 +193,7 @@ class UniquenessValidator < ActiveModel::EachValidator protected def build_relation_with_paranoia(klass, table, attribute, value) relation = build_relation_without_paranoia(klass, table, attribute, value) + return relation unless klass.respond_to?(:paranoia_column) relation.and(klass.arel_table[klass.paranoia_column].eq(nil)) end alias_method_chain :build_relation, :paranoia diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 6dae9a7e..4450ae28 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -440,6 +440,20 @@ def test_restore_with_nil_has_one_association assert hasOne.reload.deleted_at.nil? end + def test_has_one_really_destroy_with_nil + model = ParanoidModelWithHasOne.create + model.really_destroy! + + refute ParanoidModelWithBelong.unscoped.exists?(model.id) + end + + def test_has_one_really_destroy_with_record + model = ParanoidModelWithHasOne.create { |record| record.build_paranoid_model_with_belong } + model.really_destroy! + + refute ParanoidModelWithBelong.unscoped.exists?(model.id) + end + def test_observers_notified a = ParanoidModelWithObservers.create a.destroy