Skip to content

Commit

Permalink
Merge pull request rubysherpas#389 from edwardmp/AssociationNotSoftDe…
Browse files Browse the repository at this point in the history
…stroyedValidator

Added association not soft destroyed validator
  • Loading branch information
BenMorganIO authored Mar 8, 2017
2 parents b63f446 + d366099 commit 36adfec
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## 2.2.2 (Unreleased)

* [#389](https://github.com/rubysherpas/paranoia/pull/389) Added association not soft destroyed validator

_Fixes [#380](https://github.com/rubysherpas/paranoia/issues/380)_

[Edward Poot (@edwardmp)](https://github.com/edwardmp)

## 2.2.1 (2017-02-15)

* [#371](https://github.com/rubysherpas/paranoia/pull/371) Use ActiveSupport.on_load to correctly re-open ActiveRecord::Base
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ Client.restore(id, :recursive => true. :recovery_window => 2.minutes)
client.restore(:recursive => true, :recovery_window => 2.minutes)
```

Note that by default paranoia will not prevent that a soft destroyed object can't be associated with another object of a different model.
A Rails validator is provided should you require this functionality:
``` ruby
validates :some_assocation, association_not_soft_destroyed: true
```
This validator makes sure that `some_assocation` is not soft destroyed. If the object is soft destroyed the main object is rendered invalid and an validation error is added.

For more information, please look at the tests.

#### About indexes:
Expand Down
9 changes: 9 additions & 0 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,14 @@ def build_relation(klass, *args)
class UniquenessValidator < ActiveModel::EachValidator
prepend UniquenessParanoiaValidator
end

class AssociationNotSoftDestroyedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# if association is soft destroyed, add an error
if value.present? && value.deleted?
record.errors[attribute] << 'has been soft-deleted'
end
end
end
end
end
58 changes: 38 additions & 20 deletions test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def setup!
'paranoid_model_with_foreign_key_belongs' => 'parent_model_id INTEGER, deleted_at DATETIME, has_one_foreign_key_id INTEGER',
'paranoid_model_with_timestamps' => 'parent_model_id INTEGER, created_at DATETIME, updated_at DATETIME, deleted_at DATETIME',
'not_paranoid_model_with_belongs' => 'parent_model_id INTEGER, paranoid_model_with_has_one_id INTEGER',
'not_paranoid_model_with_belongs_and_assocation_not_soft_destroyed_validator' => 'parent_model_id INTEGER, paranoid_model_with_has_one_id INTEGER',
'paranoid_model_with_has_one_and_builds' => 'parent_model_id INTEGER, color VARCHAR(32), deleted_at DATETIME, has_one_foreign_key_id INTEGER',
'featureful_models' => 'deleted_at DATETIME, name VARCHAR(32)',
'plain_models' => 'deleted_at DATETIME',
Expand Down Expand Up @@ -117,9 +118,9 @@ def test_destroy_behavior_for_plain_models_callbacks
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_nil model.instance_variable_get(:@update_callback_called)
assert_nil model.instance_variable_get(:@save_callback_called)
assert_nil model.instance_variable_get(:@validate_called)

assert model.instance_variable_get(:@destroy_callback_called)
assert model.instance_variable_get(:@after_destroy_callback_called)
Expand All @@ -133,12 +134,12 @@ def test_delete_behavior_for_plain_models_callbacks
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)
assert_nil model.instance_variable_get(:@update_callback_called)
assert_nil model.instance_variable_get(:@save_callback_called)
assert_nil model.instance_variable_get(:@validate_called)
assert_nil model.instance_variable_get(:@destroy_callback_called)
assert_nil model.instance_variable_get(:@after_destroy_callback_called)
assert_nil model.instance_variable_get(:@after_commit_callback_called)
end

def test_delete_in_transaction_behavior_for_plain_models_callbacks
Expand All @@ -149,11 +150,11 @@ def test_delete_in_transaction_behavior_for_plain_models_callbacks
model.delete
end

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_nil model.instance_variable_get(:@update_callback_called)
assert_nil model.instance_variable_get(:@save_callback_called)
assert_nil model.instance_variable_get(:@validate_called)
assert_nil model.instance_variable_get(:@destroy_callback_called)
assert_nil model.instance_variable_get(:@after_destroy_callback_called)
assert model.instance_variable_get(:@after_commit_callback_called)
end

Expand Down Expand Up @@ -226,7 +227,7 @@ def test_destroy_behavior_for_custom_column_models
end

def test_default_sentinel_value
assert_equal nil, ParanoidModel.paranoia_sentinel_value
assert_nil ParanoidModel.paranoia_sentinel_value
end

def test_without_default_scope_option
Expand Down Expand Up @@ -375,7 +376,7 @@ def test_delete_behavior_for_callbacks
model = CallbackModel.new
model.save
model.delete
assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
assert_nil model.instance_variable_get(:@destroy_callback_called)
end

def test_destroy_behavior_for_callbacks
Expand Down Expand Up @@ -948,8 +949,8 @@ def test_callbacks_for_counter_cache_column_update_on_destroy
parent_model_with_counter_cache_column = ParentModelWithCounterCacheColumn.create
related_model = parent_model_with_counter_cache_column.related_models.create

assert_equal nil, related_model.instance_variable_get(:@after_destroy_callback_called)
assert_equal nil, related_model.instance_variable_get(:@after_commit_on_destroy_callback_called)
assert_nil related_model.instance_variable_get(:@after_destroy_callback_called)
assert_nil related_model.instance_variable_get(:@after_commit_on_destroy_callback_called)

related_model.destroy

Expand All @@ -964,6 +965,18 @@ def test_uniqueness_for_unparanoid_associated
related.valid?
end

def test_assocation_not_soft_destroyed_validator
notParanoidModel = NotParanoidModelWithBelongsAndAssocationNotSoftDestroyedValidator.create
parentModel = ParentModel.create
assert notParanoidModel.valid?

notParanoidModel.parent_model = parentModel
assert notParanoidModel.valid?
parentModel.destroy
assert !notParanoidModel.valid?
assert notParanoidModel.errors.full_messages.include? "Parent model has been soft-deleted"
end

# TODO: find a fix for Rails 4.1
if ActiveRecord::VERSION::STRING !~ /\A4\.1/
def test_counter_cache_column_update_on_really_destroy
Expand All @@ -982,8 +995,8 @@ def test_callbacks_for_counter_cache_column_update_on_really_destroy!
parent_model_with_counter_cache_column = ParentModelWithCounterCacheColumn.create
related_model = parent_model_with_counter_cache_column.related_models.create

assert_equal nil, related_model.instance_variable_get(:@after_destroy_callback_called)
assert_equal nil, related_model.instance_variable_get(:@after_commit_on_destroy_callback_called)
assert_nil related_model.instance_variable_get(:@after_destroy_callback_called)
assert_nil related_model.instance_variable_get(:@after_commit_on_destroy_callback_called)

related_model.really_destroy!

Expand Down Expand Up @@ -1265,6 +1278,11 @@ class NotParanoidModelWithBelong < ActiveRecord::Base
belongs_to :paranoid_model_with_has_one
end

class NotParanoidModelWithBelongsAndAssocationNotSoftDestroyedValidator < NotParanoidModelWithBelong
belongs_to :parent_model
validates :parent_model, association_not_soft_destroyed: true
end

class FlaggedModel < PlainModel
acts_as_paranoid :flag_column => :is_deleted
end
Expand Down

0 comments on commit 36adfec

Please sign in to comment.