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

Add link table test for update method call #442

Open
wants to merge 5 commits into
base: core
Choose a base branch
from
Open
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
40 changes: 40 additions & 0 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,46 @@ def restore(id_or_ids, opts = {})
end
end

def paranoia_update(attributes)
attributes = attributes.with_indifferent_access
object_class = self.class

has_many_through_associations = object_class.reflect_on_all_associations.select do |association|
association.is_a?(ActiveRecord::Reflection::ThroughReflection)
end

transaction do
run_callbacks(:update) do
has_many_through_associations.each do |association|
association_key =
"#{object_class.reflect_on_association(association.name).foreign_key.pluralize}"
link_table_class = association.through_reflection.klass
next if !attributes.key?(association_key) || !link_table_class.paranoid?

association_id_array = attributes[association_key].reject(&:blank?)
link_table_plural_name = association.through_reflection.plural_name.to_sym
object_key = "#{object_class.reflect_on_association(link_table_plural_name).foreign_key}"
object_primary_key = association.active_record.primary_key.to_sym

link_table_objects_to_be_soft_deleted =
link_table_class
.where(object_key => public_send(object_primary_key))
.where.not(association_key.singularize => association_id_array)
link_table_objects_to_be_soft_deleted.map(&:paranoia_destroy)
end

self.attributes = attributes
self.save
end
end
end
alias_method :update, :paranoia_update

def paranoia_update!(attributes)
paranoia_update(attributes) ||
raise(ActiveRecord::RecordNotDestroyed.new("Failed to update the record", self))
end

def paranoia_destroy
with_transaction_returning_status do
result = run_callbacks(:destroy) do
Expand Down
172 changes: 171 additions & 1 deletion test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ def setup!
'association_with_abort_models' => 'deleted_at DATETIME',
'related_models' => 'parent_model_id INTEGER, parent_model_with_counter_cache_column_id INTEGER, deleted_at DATETIME',
'asplode_models' => 'parent_model_id INTEGER, deleted_at DATETIME',
'employers' => 'name VARCHAR(32), deleted_at DATETIME',
'employers' => 'name VARCHAR(32), employee_count INTEGER, date_established DATETIME, number_of_offices INTEGER, deleted_at DATETIME',
'employees' => 'deleted_at DATETIME',
'jobs' => 'employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME',
'purchases' => 'deleted_at DATETIME',
'purchase_orders' => 'employer_id INTEGER NOT NULL, purchase_id INTEGER NOT NULL, deleted_at DATETIME',
'suppliers' => 'deleted_at DATETIME',
'accounts' => 'employer_id INTEGER NOT NULL, supplier_id INTEGER NOT NULL, deleted_at DATETIME',
'candidates' => 'deleted_at DATETIME',
'interviews' => 'employer_id INTEGER NOT NULL, candidate_id INTEGER NOT NULL, deleted_at DATETIME',
'custom_column_models' => 'destroyed_at DATETIME',
'custom_sentinel_models' => 'deleted_at DATETIME NOT NULL',
'non_paranoid_models' => 'parent_model_id INTEGER',
Expand Down Expand Up @@ -488,6 +494,93 @@ def test_default_scope_for_has_many_through_relationships
assert_equal 0, employee.employers.count
end

def test_link_table_retains_records_with_update_call
employer = employer_with_associations

assert_equal 3, employer.jobs.count
assert_equal 3, employer.employees.count
assert_equal 1, employer.employees.first.jobs.count
assert_equal 1, employer.employees.second.jobs.count

employer.update(employee_ids: [3,4,5], name: 'Google')

assert_equal 5, Employee.count
assert_equal [3,4,5], Job.all.map(&:employee_id)
assert_equal [1,2], Job.deleted.map(&:employee_id)
assert_equal 'Google', employer.name
end

def test_link_table_does_not_retains_records_with_update_call_when_not_paranoid
employer = employer_with_associations

assert_equal 2, employer.purchases.count
assert_equal 2, employer.purchase_orders.count

employer.update(purchase_ids: [], name: 'Apple')

assert_equal 0, PurchaseOrder.unscoped.count
assert_equal [1,2], Purchase.all.map(&:id)
assert_equal 'Apple', employer.name
end

def test_update_on_associations_with_dependent_destroy_when_not_paranoid
employer = employer_with_associations

assert_equal 2, PurchaseOrder.count
assert_equal 2, Purchase.count

employer.update(purchase_ids: [])

assert_equal 0, PurchaseOrder.count
assert_equal 2, Purchase.count
end

def test_update_on_associations_with_dependent_destroy_when_paranoid
employer = employer_with_associations

assert_equal 1, Interview.count
assert_equal 1, Candidate.count

employer.update(candidate_ids: [])

assert_equal 1, Interview.unscoped.count
assert_equal 1, Candidate.count
end

def test_update_on_associations_without_dependent_destroy_when_paranoid
employer = employer_with_associations

assert_equal 3, Job.count
assert_equal 5, Employee.count

employer.update(employee_ids: [])

assert_equal 0, Job.count
assert_equal 5, Employee.count
end

def test_update_on_associations_without_dependent_destroy_when_not_paranoid
employer = employer_with_associations

assert_equal 1, Account.count
assert_equal 1, Supplier.count

employer.update(supplier_ids: [])

assert_equal 0, Account.unscoped.count
assert_equal 1, Supplier.count
end

def test_update_on_top_level_attributes
employer = employer_with_associations

employer.update(name: 'Apple', employee_count: 123, date_established: Date.yesterday)
assert_equal 'Apple', employer.name
assert_equal 123, employer.employee_count
assert_equal Date.yesterday, employer.date_established
assert_equal 6, employer.number_of_offices # remains unchanged
end

def test_delete_behavior_for_callbacks
model = CallbackModel.new
model.save
Expand Down Expand Up @@ -1178,9 +1271,41 @@ def test_counter_cache_column_on_restore
end

private

def get_featureful_model
FeaturefulModel.new(:name => "not empty")
end

def employer_with_associations
employer = Employer.create(
name: 'Bellroy',
employee_count: 100,
date_established: Date.today,
number_of_offices: 6
)

employee_1 = Employee.create(id: 1)
employee_2 = Employee.create(id: 2)
employee_3 = Employee.create(id: 3)
employee_4 = Employee.create(id: 4)
employee_5 = Employee.create(id: 5)
job_1 = Job.create :employer => employer, :employee => employee_1
job_2 = Job.create :employer => employer, :employee => employee_2
job_3 = Job.create :employer => employer, :employee => employee_3

purchase_1 = Purchase.create(id: 1)
purchase_2 = Purchase.create(id: 2)
purchase_order_1 = PurchaseOrder.create :employer => employer, :purchase => purchase_1
purchase_order_2 = PurchaseOrder.create :employer => employer, :purchase => purchase_2

candidate = Candidate.create
inteview = Interview.create :employer => employer, :candidate => candidate

supplier = Supplier.create
account = Account.create :employer => employer, :supplier => supplier

employer
end
end

# Helper classes
Expand Down Expand Up @@ -1336,11 +1461,24 @@ def set_after_commit_on_destroy_callback_called
end
end

# Classes | Paranoid? | Dependant Destroy?
#--------------------------------------------------------
# Account/Supplier | X | X
# Employee/Job | ✓ | X
# Purchase/PurchaseOrder | X | ✓
# Interview/Candidate | ✓ | ✓

class Employer < ActiveRecord::Base
acts_as_paranoid
validates_uniqueness_of :name
has_many :jobs
has_many :employees, :through => :jobs
has_many :purchase_orders, dependent: :destroy
has_many :purchases, dependent: :destroy, :through => :purchase_orders
has_many :interviews, dependent: :destroy
has_many :candidates, dependent: :destroy, :through => :interviews
has_many :accounts
has_many :suppliers, :through => :accounts
end

class Employee < ActiveRecord::Base
Expand All @@ -1355,6 +1493,38 @@ class Job < ActiveRecord::Base
belongs_to :employee
end

class PurchaseOrder < ActiveRecord::Base
belongs_to :employer
belongs_to :purchase
end

class Purchase < ActiveRecord::Base
has_many :purchase_orders
has_many :employers, :through => :purchase_orders
end

class Interview < ActiveRecord::Base
acts_as_paranoid
belongs_to :employer
belongs_to :candidate
end

class Candidate < ActiveRecord::Base
acts_as_paranoid
has_many :interviews
has_many :employers, :through => :interviews
end

class Account < ActiveRecord::Base
belongs_to :employer
belongs_to :supplier
end

class Supplier < ActiveRecord::Base
has_many :accounts
has_many :employers, :through => :accounts
end

class CustomColumnModel < ActiveRecord::Base
acts_as_paranoid column: :destroyed_at
end
Expand Down