Deletion of joining records not being audited on Collection Associations
Hey audit gem maintainers 👋
Found a small issue that I'd love your feedback on. Also, really appreciate all the time the maintainers of this gem put into this great open source project.
Model Structure
Say I have the following model structure -
- An
Employeecan have manyProjects - A
Projectcan have manyEmployees - Both models are associated to each other
:througha joining table calledEmployeeProject
The rails models look like this:
class Employee < ApplicationRecord
audited
has_many :employee_projects, dependent: :destroy
has_many :projects, through: :employee_projects
accepts_nested_attributes_for :projects, allow_destroy: true
end
class Project < ApplicationRecord
audited
has_many :employee_projects, dependent: :destroy
has_many :employees, through: :employee_projects
accepts_nested_attributes_for :employees, allow_destroy: true
end
class EmployeeProject < ApplicationRecord
audited
belongs_to :employee
belongs_to :project
validates :employee, presence: true
validates :project, presence: true
end
The Issue
Rails provides collection association helpers on models that use the has_many :through association.
So in the above example rails automatically provides the following -
-
@employee.project_ids= -
@project.employee_ids=
These helpers are absolute - they automatically add and remove any necessary joining table records based on the array you provide them. So @employe.project_ids = [1, 2] would ensure that only projects 1, and 2 are joined to this @employee record. If any other records exist, they will be destroyed when the model is saved. (EDIT: It actually calls save itself automatically, so changes take effect immediately)
The issue is that the Audit gem correctly audits any newly created joining records using the above helpers, but it does not correctly audit any destroyed joining records.
Example
Initialize models and note that there are no audit records
> @employee = Employee.first
> @project = Project.first
> Audited::Audit.count # => 0
Associate @employee and @project using the project_ids= collection association helper.
This correctly creates an audit record for the new EmployeeProject joining record
> @employee.project_ids = [@project.id] # => [15]
> @employee.save # => true
> Audited::Audit.count # => 1
> Audited::Audit.first.auditable_type # => "EmployeeProject"
Now disassociate @employee and @project which will delete the EmployeeProject joining record.
This should create another Audit record for the deletion of the joining record... but it doesn't
> @employee.project_ids = [] # => []
> @employee.save # => true
> Audited::Audit.count # => 1
I had the same problem and found a solution.
For you, adding "dependent: :destroy" to your "has_many through" statement should make it work:
class Employee < ApplicationRecord
has_many :projects, through: :employee_projects, dependent: :destroy
end
Note that destroying the the employee does not destroy the projects. This is more of a Rails oddity.
Also, it's a bit weird you have audited on all three models. I would suggest changing it to:
class Employee < ApplicationRecord
audited ## Assuming Employee is the main model where you will compile "own_and_associated_audits" data
has_associated_audits
has_many :employee_projects, dependent: :destroy
has_many :projects, through: :employee_projects, dependent: :destroy ## <== CHANGED
accepts_nested_attributes_for :projects, allow_destroy: true
end
class Project < ApplicationRecord
## <== CHANGED, removed audited unless you need it from this model
has_many :employee_projects, dependent: :destroy
has_many :employees, through: :employee_projects
accepts_nested_attributes_for :employees, allow_destroy: true
end
class EmployeeProject < ApplicationRecord
audited associated_with: :employee ## <== CHANGED
belongs_to :employee
belongs_to :project
validates :employee, presence: true
validates :project, presence: true
end
Hi @chase439 and @abhchand ... adding dependent: :destroy didn't solve this for me. I can see the SQL log entry for deleting the entry in the joining table but no "destroy" audit is created in that case.
Any further hints?
THX, mscdit