Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle #delete_all #566

Merged
merged 3 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rails: ["edge", "~> 7.2.0", "~> 7.1.0", "~> 7.0.0", "~> 6.1.0"]
rails: ["~> 7.2.0", "~> 7.1.0", "~> 7.0.0", "~> 6.1.0"]
ruby: ["3.3","3.2", "3.1", "3.0", "2.7"]
exclude:
- rails: "~> 7.2.0"
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,21 @@ end
# => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>
```

#### delete_all:

The gem supports `delete_all` method, however it is disabled by default, to enabled add this in your `environment` file

``` ruby
Paranoia.delete_all_enabled = true
```
alternatively, you can enable/disable it for specific models as follow:

``` ruby
class User < ActiveRecord::Base
acts_as_paranoid(delete_all_enabled: true)
end
```

## Acts As Paranoid Migration

You can replace the older `acts_as_paranoid` methods as follows:
Expand Down
55 changes: 36 additions & 19 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@
end

module Paranoia
@@default_sentinel_value = nil

# Change default_sentinel_value in a rails initializer
def self.default_sentinel_value=(val)
@@default_sentinel_value = val
end

def self.default_sentinel_value
@@default_sentinel_value
class << self
# Change default values in a rails initializer
attr_accessor :default_sentinel_value,
:delete_all_enabled
end

def self.included(klazz)
Expand Down Expand Up @@ -58,6 +54,16 @@ def restore(id_or_ids, opts = {})
end
ids.map { |id| only_deleted.find(id).restore!(opts) }
end

def paranoia_destroy_attributes
{
paranoia_column => current_time_from_proper_timezone
}.merge(timestamp_attributes_with_current_time)
end

def timestamp_attributes_with_current_time
timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
end
end

def paranoia_destroy
Expand Down Expand Up @@ -200,18 +206,10 @@ def each_counter_cached_associations
def paranoia_restore_attributes
{
paranoia_column => paranoia_sentinel_value
}.merge(timestamp_attributes_with_current_time)
}.merge(self.class.timestamp_attributes_with_current_time)
end

def paranoia_destroy_attributes
{
paranoia_column => current_time_from_proper_timezone
}.merge(timestamp_attributes_with_current_time)
end

def timestamp_attributes_with_current_time
timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
end
delegate :paranoia_destroy_attributes, to: 'self.class'

def paranoia_find_has_one_target(association)
association_foreign_key = association.options[:through].present? ? association.klass.primary_key : association.foreign_key
Expand Down Expand Up @@ -262,6 +260,14 @@ def restore_associated_records(recovery_window_range = nil)
end
end

module Paranoia::Relation
def paranoia_delete_all
update_all(klass.paranoia_destroy_attributes)
end

alias_method :delete_all, :paranoia_delete_all
end

ActiveSupport.on_load(:active_record) do
class ActiveRecord::Base
def self.acts_as_paranoid(options={})
Expand All @@ -276,9 +282,10 @@ def self.acts_as_paranoid(options={})
alias_method :really_destroyed?, :destroyed?
alias_method :really_delete, :delete
alias_method :destroy_without_paranoia, :destroy
class << self; delegate :really_delete_all, to: :all end

include Paranoia
class_attribute :paranoia_column, :paranoia_sentinel_value
class_attribute :paranoia_column, :paranoia_sentinel_value, :delete_all_enabled

self.paranoia_column = (options[:column] || :deleted_at).to_s
self.paranoia_sentinel_value = options.fetch(:sentinel_value) { Paranoia.default_sentinel_value }
Expand All @@ -297,6 +304,16 @@ class << self; alias_method :without_deleted, :paranoia_scope end
after_restore {
self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers)
}

self.delete_all_enabled = options[:delete_all_enabled] || Paranoia.delete_all_enabled

if self.delete_all_enabled
"#{self}::ActiveRecord_Relation".constantize.class_eval do
alias_method :really_delete_all, :delete_all

include Paranoia::Relation
end
end
end

# Please do not use this method in production.
Expand Down
70 changes: 68 additions & 2 deletions test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,71 @@ def test_has_one_with_scope_not_restored
assert_equal 1, ParanoidHasOneWithScope.count # gamma deleted
end

def test_delete_all_disabled_by_default
assert_nil ParanoidModel.delete_all_enabled

(0...3).each{ ParanoidModel.create }
assert_equal 3, ParanoidModel.count
ParanoidModel.delete_all
assert_equal 0, ParanoidModel.count
assert_equal 0, ParanoidModel.unscoped.count
end

def test_delete_all_called_on_class
assert Employee.delete_all_enabled

(0...3).each{ Employee.create }
assert_equal 3, Employee.count
Employee.delete_all
assert_equal 0, Employee.count
assert_equal 3, Employee.unscoped.count
end

def test_delete_all_called_on_relation
assert Employee.delete_all_enabled

(0...3).each{ Employee.create }
assert_equal 3, Employee.count
Employee.where(id: 1).delete_all
assert_equal 2, Employee.count
assert_equal 3, Employee.unscoped.count
end

def test_really_delete_all_called_on_class
assert Employee.delete_all_enabled

(0...3).each{ Employee.create }
assert_equal 3, Employee.count
Employee.really_delete_all
assert_equal 0, Employee.count
assert_equal 0, Employee.unscoped.count
end

def test_delete_all_called_on_relation
assert Employee.delete_all_enabled

(0...3).each{ Employee.create }
assert_equal 3, Employee.count
Employee.where(id: 1).really_delete_all
assert_equal 2, Employee.count
assert_equal 2, Employee.unscoped.count
end

def test_update_has_many_through_relation_delete_associations
employer = Employer.create
employee1 = Employee.create
employee2 = Employee.create
job = Job.create :employer => employer, :employee => employee1

assert_equal 1, employer.jobs.count
assert_equal 1, employer.jobs.with_deleted.count

employer.update(employee_ids: [employee2.id])

assert_equal 1, employer.jobs.count
assert_equal 2, employer.jobs.with_deleted.count
end

private
def get_featureful_model
FeaturefulModel.new(:name => "not empty")
Expand Down Expand Up @@ -1418,16 +1483,17 @@ class Employer < ActiveRecord::Base
acts_as_paranoid
validates_uniqueness_of :name
has_many :jobs
has_many :employees, :through => :jobs
has_many :employees, :through => :jobs, dependent: :destroy
end

class Employee < ActiveRecord::Base
acts_as_paranoid
acts_as_paranoid(delete_all_enabled: true)
has_many :jobs
has_many :employers, :through => :jobs
end

class Job < ActiveRecord::Base
acts_as_paranoid(delete_all_enabled: true)
acts_as_paranoid
belongs_to :employer
belongs_to :employee
Expand Down