Rails :dependent => destroy with conditions - ruby-on-rails

Say I have dogs, leashes, and owners... If I destroy a leash I want to destroy the dog too.. but not if the dog has an owner..

You don't want to use :dependent => :destroy here, but rather the before_destroy callback like so:
#leash.rb
before_destroy :destroy_dog
def destroy_dog
dog.destroy unless dog.owner
end

class Book < ApplicationRecord
belongs_to :author, -> { where active: true },
dependent: :destroy
end
works for has_many too, in which it destroys the objects according to the where condition

Related

ruby on rails Nested structure not deleting the related objects

user model
class User < ApplicationRecord
has_many :posts
accepts_nested_attributes_for :posts, allow_destroy: true
end
post model
class Post < ApplicationRecord
belongs_to :user
accepts_nested_attributes_for :user, allow_destroy: true
end
user controller
class Api::UsersController < ApiController
def destroy
User.destroy(params[:id])
end
end
I thought if I destroy the user using destroy, all the posts related to user will be deleted automatically.
But still nothing is deleted.
What I am doing wrong here?
You can use dependent: :delete_all
has_many :posts, :dependent => :delete_all

Howto use callbacks in a has_many through association?

I have a Task model associated to a Project model via has_many through and need to manipulate the data before delete/insert via the association.
Since "Automatic deletion of join models is direct, no destroy callbacks are triggered." i can not use callbacks for this.
In Task i need all project_ids to calculate a value for Project after Task is saved.
How can i disable delete or change delete to destroy on has_many through association?
What is best practise for this problem?
class Task
has_many :project_tasks
has_many :projects, :through => :project_tasks
class ProjectTask
belongs_to :project
belongs_to :task
class Project
has_many :project_tasks
has_many :tasks, :through => :project_tasks
Seems like i have to use associations callbacks before_add, after_add, before_remove or after_remove
class Task
has_many :project_tasks
has_many :projects, :through => :project_tasks,
:before_remove => :my_before_remove,
:after_remove => :my_after_remove
protected
def my_before_remove(obj)
...
end
def my_after_remove(obj)
...
end
end
This is what I did
in the model:
class Body < ActiveRecord::Base
has_many :hands, dependent: destroy
has_many :fingers, through: :hands, after_remove: :touch_self
end
in my Lib folder:
module ActiveRecord
class Base
private
def touch_self(obj)
obj.touch && self.touch
end
end
end
Updating joins model associations, Rails add and remove records on the collection. To remove the records, Rails use the delete method and this one will not call any destroy callback.
You can force Rails to call destroy instead delete when is removing records.
To do that, install the gem replace_with_destroy and pass the option replace_with_destroy: true to the has_many association.
class Task
has_many :project_tasks
has_many :projects, :through => :project_tasks,
replace_with_destroy: true
...
end
class ProjectTask
belongs_to :project
belongs_to :task
# any destroy callback in this model will be executed
#...
end
class Project
...
end
With this, you ensure Rails invoke all the destroy callbacks. This could be very useful if you are using paranoia.
It seems like adding dependent: :destroy to has_many :through relation will destroy the join model (instead of delete). This is because CollectionAssociation#delete internally refers to :dependent option to determine whether the passed records should be deleted or destroyed.
So in your case,
class Task
has_many :project_tasks
has_many :projects, :through => :project_tasks, :dependent => :destroy
end
should work.

Determine what ids were removed from array in Rails after_save callback?

I am trying to figure out how i can tell what has changed in an array in the after save callback. Here is an example of code i am using:
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy
has_many :subusers, :through => :user_maps, :dependent => :destroy
has_many :inverse_user_maps, :class_name => "UserMap", :foreign_key => "subuser_id"
has_one :parent, :through => :inverse_user_maps, :source => :user
after_save :remove_subusers
def remove_subusers
if self.subuser_ids_were != self.subuser_ids
leftover = self.subuser_ids_were - self.subuser_ids
leftover.each do |subuser|
subuser.destroy
end
end
end
end
class UserMap < ActiveRecord::Base
belongs_to :user
belongs_to :subuser, :class_name => "User"
end
I am removing the subusers with the after_save callback because i could not get the dependent destroy feature to work through user_maps. Does anyone have any ideas on a way to do this?
Thanks!
You can use the Dirty module accessors http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html as suggested in Determine what attributes were changed in Rails after_save callback?
In your case the handler you have for after_save will have access to subusers_change which is an array of two elements, first being the previous value and second being the new value.
although not strictly the answer to your question, I think you maybe able to get :dependent => :destroy working if you try the following...
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy
has_many :subusers, :through => :user_maps # removing the :dependent => :destroy option
end
class UserMap < ActiveRecord::Base
belongs_to :user
belongs_to :subuser, :class_name => "User", :dependent => :destroy # add it here
end
By moving the :dependent => :destroy option to the belongs_to association in the UserMap model you set up a cascading delete via the UserMap#destroy method. In other words, calling User#destroy will call UserMap#destroy for each UserMap record, which will in turn call sub_user.destroy for its sub_user record.
EDIT
Since the solution above didn't work, my next suggestion would be to add a callback to the user_maps association, however this comes with a warning that I will add after
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy, :before_remove => :remove_associated_subuser
def remove_associated_subuser(user_map)
user_map.subuser.destroy
end
end
WARNINGS
1) Using a before_remove callback will mean that the user_map.destroy function won't be called if there is an error with the callback
2) You will have to destroy your UserMap record using the method on the User class for example...
# this will fire the callback
u = User.first
u.user_maps.destroy(u.user_maps.first)
# this WONT fire the callback
UserMap.first.destroy
All things considered, this would make me nervous. I would first try modifying your code to make the associations a little less coupled to the same tables, so the :dependent => :destroy option can work, and if you can't do that, add a cascade delete constraint on to the database, at least then your associations will always be removed regardless of where / how you destroy it in your rails app.

Rails - AciveRecord use :dependent => :destroy on condition

What will be the best/DRY way to destroy all the dependents of an object based on a condition. ?
Ex:
class Worker < ActiveRecord::Base
has_many :jobs , :dependent => :destroy
has_many :coworkers , :dependent => :destroy
has_many :company_credit_cards, :dependent => :destroy
end
condition will be
on Destroy:
if self.is_fired?
#Destroy dependants records
else
# Do not Destroy records
end
Is There a Way to use Proc in the :dependent condition.
I have found the methods to destroy the dependents individually, but this is not DRY and flexible for further associations,
Note: I have made up the example.. not an actual logic
No. You should remove :dependent => :destroy and add after_destroy callback where you can write any logic you want.
class Worker < ActiveRecord::Base
has_many :jobs
has_many :coworkers
has_many :company_credit_cards
after_destroy :cleanup
private
def cleanup
if self.is_fired?
self.jobs.destroy_all
self.coworkers.destroy_all
self.company_credit_cards.destroy_all
end
end
end

How to delete nested objects in Rails3?

How can I delete nested objects in a form? I found out that I need to add :allow_destroy in the parent model at the accepts_nested_attributes_for directive.
Further, I want to restrict the deletion. A nested object only should be deleted, if the parent object is the only one that retains the association.
Example:
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships
end
Explanation: A company can host many internships. Therefore, I do not want to delete the company record as long as there is at least one other internship associated with it.
You could use dependent => :destroy
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships, :dependent => :destroy
end
If you return false in a before_destroy filter, then the destroy action will be blocked. So we can check to see if there are any internships associated to the company, and block it if so. This is done in the company model.
class Company < ActiveRecord::Base
has_many :internships
before_destroy :ensure_no_internships
private
def ensure_no_internships
return false if self.internships.count > 0
end
end

Resources