I have a has_many :through model that works perfectly.
has_many :varietals
has_many :grapes, :through => :varietals, :dependent => :destroy
I would like to call another action instead of :destroy. In fact, I don't want to nullify the item OR destroy it, I want to update the record status field from 1 to 0 instead of destroy the record.
How to call a custom method instead of destroy ? I suppose I can do that in the model itself... Thanks.
Where to put this method ? In the master model or in the model where the record will be destroyed ?
EDIT:
I'm sorry but I think I didn't enough explain my problem. My problem is not only to so something after the master model is destroyed. I want to custom the destroy action in the Varietal model itself even if the master record is not destroyed.
Something like:
class Varietal < ActiveRecord::Base
private
def destroy
self.update_attributes(:status => 0)
end
end
Actually this action is not called...
You can use before_destroy to put your custom logic there. E.g.,
before_destroy :reset_status
def reset_status
...
end
Check here for more details.
You just need add a callback on before_destroy or after_destroy and manipulate your associations. By example
after_destroy :do_on_grapes
def do_on_grapes
grapes.map(&:to_do)
end
has_many :dependent is limited to only a few options. According to the documentation:
:dependent If set to :destroy all the associated objects are destroyed
alongside this object by calling their destroy method. If set to
:delete_all all associated objects are deleted without calling their
destroy method. If set to :nullify all associated objects’ foreign
keys are set to NULL without calling their save callbacks. If set to
:restrict this object raises an ActiveRecord::DeleteRestrictionError
exception and cannot be deleted if it has any associated objects.
If using with the :through option, the association on the join model
must be a belongs_to, and the records which get deleted are the join
records, rather than the associated records.
It looks like you would need to alter the destroy method to update the status field.
I believe that good approach to solve your problem is to provide a custom destroy method. There are several responses to questions like these, but you should keep in mind that ActiveRecord and Relationships like:
class Image < ActiveRecord::Base
has_many :comments, dependent: :destroy
use callback mechanisms that trigger destroy chaining to your relations, too. Usually you should preserve this mechanism and add it to your custom implementation. E.g.
def destroy
self.update deleted_at: Time.now
run_callbacks :destroy
end
You can read this post, too:
Triggering dependent: :destroy with overridden destroy-method
Related
The after_commit callback is not being triggered when the has_many relationship is updated and a record is destroyed.
I have a relationship
class Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises
class DoctorExpertise
belongs_to :doctor_profile
belongs_to :expertise
after_commit :do_something
def do_something
# not called when record destroyed
end
in my controller I use the following method to update the has_many relationship
def create
doc = DoctorProfile.find(params[:doctor_id])
doc.expertise_ids = params[:expertise_ids].select do |x|
x.to_i > 0
end
doc.save!
render json: doc.expertises
end
I understand that I should be using update and destroy on the relationship. However, why is after_commit not getting called on a record when it is destroyed?
I'm guessing it has something to do with the way I'm setting doc.expertise_ids not triggering the callback. However I'm unable to find any documentation about this method except for briefly here. Is there documentation that confirms or denies this suspicion or is there something else going on?
From the RailsGuides that you linked:
Automatic deletion of join models is direct, no destroy callbacks are triggered.
Although it doesn't state about after_commit it's quite likely that isn't being fired too
I think the answer you are looking for is here:
Howto use callbacks in a has_many through association?
You need to user after_remove within the has_many declaration
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.
Solution 1
One way to invoke some callbacks when add or remove association is using Association Callbacks.
class Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises
before_remove: :my_before_remove,
after_remove: my_after_remove
def my_before_remove(doctor_profile)
...
end
def my_after_remove(doctor_profile)
...
end
end
Solution 2
Force Rails 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 Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises,
replace_with_destroy: true
...
end
class DoctorExpertise
belongs_to :doctor_profile
belongs_to :expertise
after_commit :do_something
def do_something
# this will be called when updating Expertise.doctor_profiles
# because use destroyed instead delete to remove the record
end
With this, you ensure Rails invoke all the destroy callbacks.
Just add dependent: :destroy like so
has_many :doctor_profiles, through: :doctor_expertises, dependent: :destroy
I know it's somewhat misleading to have dependent destroy on has_many through, but it will trigger destroy callback and it will not destroy the base records, just the join records. Reference: https://github.com/collectiveidea/audited/issues/246
I've a Rails 4 app that uses Postgresql database. I'm using UUIDs as id for my models.
Everything works as expected, I'm trying to set a dependant destroy has many relation, and the "dependant destroy" is not working.
Is there any incompativility between postgress UUIDs and dependent destroy? I need to set foreign keys?
I expalin a bit of my code:
Navigation through models is working correclty
To define the has_many I'm using
has_many :some_models, dependent: :destroy
My migrations are something like:
def change
create_table :my_model, id: :uuid do |t|
To test, I'm using console. I create a relation, delete the "some_models" and the main model is not deleted.
Thanks
You are thinking of the association backwards. dependent: destroy means: When I destroy a parent record, destroy the children that are associated with that record. Here's a contrived example:
class User
has_many :photos, dependent: :destroy
end
When the user is deleted, you want their photos to also be deleted.
If you really want to delete a parent record when a child is deleted, you can do so from the before_destroy callback like so:
class Photo
before_destroy :delete_parent_user
def delete_parent_user
user.destroy if self.user
end
end
Note that other children may still be pointing to that parent record if this is a has_many relationship so this may not be advisable.
dependent: :destroy only destroys child records. When you destroy my_model record, all some_model records belonging to it will be destroyed.
I've got a record in my Rails app with an after_destroy hook that needs to be aware of why the record gets destroyed. More specifically, if the record is being destroyed in a cascade because its parent says dependent: :destroy, it needs to do things differently than if the record was individually destroyed.
What I tried to do is to see if its parent was destroyed?, only to figure out that dependent: :destroy callbacks are done before the parent is destroyed. Which makes sense because it should be able to fail. (i.e. restrict).
So, how do I do this?
Solution #1
If your model is simple enough and you don't need to invoke any callbacks in the child relation, you can just use dependent: delete_all in the parent.
Solution #2
For more complex scenarios you can use destroyed_by_association, which returns a ActiveRecord::Reflection::HasManyReflection object when it's part of cascade, or nil otherwise:
after_destroy :your_callback
def your_callback
if destroyed_by_association
# this is part of a cascade
else
# isolated deletion
end
end
I just tried this in Rails 4.2 and it works.
Source: https://github.com/rails/rails/issues/12828#issuecomment-28142658
One way to do this is using the before_destroy callback in the parent object to mark all child objects as destroyed through parent destroy. Like this:
class YourClass
before_destroy :mark_children
...
...
def mark_children
[:association1, :association2].each do |association| # Array should include association names that hate :dependent => :destroy option
self.send(association).each do |child|
# mark child object as deleted by parent
end
end
end
end
You can also use ActiveRecord reflections to determine automatically which associations are marked as :dependent => :destroy. Doing this is helpful when you need this function in many classes.
I have the following model:
class PhoneNumber < ActiveRecord::Base
has_many :personal_phone_numbers, :dependent => :destroy
has_many :people, :through => :personal_phone_numbers
end
I want to set up an observer to run an action in a delayed_job queue, which works for the most part, but with one exception. I want the before_destroy watcher to grab the people associated with the phone number, before it is destroyed, and it is on those people that the delayed job actually works.
The problem is, when a phone number is destroyed, it destroys the :personal_phone_numbers record first, and then triggers the observer when it attempts to destroy the phone number. At that point, it's too late.
Is there any way to observe the destroy action before dependent records are deleted?
While this isn't ideal, you could remove the :dependent => :destroy from the personal_phone_numbers relationship, and delete them manually in the observer after operating on them.
However, I think that this issue might be showing you a code smell. Why are you operating on people in an observer on phone number. It sounds like that logic is better handled in the join model.
Use alias_method to intercept the destroy call?
class PhoneNumber < ActiveRecord::Base
has_many :personal_phone_numbers, :dependent => :destroy
has_many :people, :through => :personal_phone_numbers
alias_method :old_destroy, :destroy
def destroy(*args)
*do your stuff here*
old_destroy(*args)
end
end
It sounds like your problem in a nutshell is that you want to gather and act on a collection of Person when a PersonalPhoneNumber is destroyed. This approach may fit the bill!
Here is an example of a custom callback to collect Person models. Here it's an instance method so we don't have to instantiate a PersonalPhoneNumberCallbacks object in the ActiveRecord model.
class PersonalPhoneNumberCallbacks
def self.after_destroy(personal_phone_number)
# Something like people = Person.find_by_personal_phone_number(personal_phone_number)
# Delayed Job Stuff on people
end
end
Next, add the callback do your ActiveRecord model:
class PersonalPhoneNumber < ActiveRecord::Base
after_destroy PictureFileCallbacks
end
Your after_destroy callback will have the model passed down and you can act on its data. After the callback chain is complete, it will be destroyed.
References
http://guides.rubyonrails.org/active_record_validations_callbacks.html#relational-callbacks
http://guides.rubyonrails.org/association_basics.html#association-callbacks
You can use a before_destroy callback in the model, then grab the data and do whatever operation you need to before destroy the parent. Something like this example should be what you are looking for:
class Example < ActiveRecord::Base
before_destroy :execute_random_method
private
def execute_random_method
...
end
handle_asynchronously :execute_random_method
A bit old but thought I'd share that rails now has the nice 'prepend' option for the before_destroy callback now. This goes follows the same line of thought with tomciopp had but allows you to define the before_destroy whereever in the class.
before_destroy :find_associated_people, prepend: true
def find_associated_people
# using phone number, find people
end
I have the following model associations:
class Slider < ActiveRecord::Base
has_one :featured_happening, :as => :featured_item, :dependent => :destroy
before_destroy :destroy_featured_happening
after_create :create_featured_happening
end
class FeaturedHappening < ActiveRecord::Base
belongs_to :featured_item, :polymorphic => true
end
When I destroy a Slider object I thought the dependent => :destroy would automatically destroy the featured_item but it does not.
Slider controller
def destroy
slider = Slider.find(params[:id])
slider.delete
render(:status => :ok, :nothing => true )
end
So then I tried a callback with before_destroy to manually delete the featured_item when a slider object is destroyed and nothing is getting called.
How can I get the featured_item to be deleted when I delete a slider object? Using Rails 3.2.
You just observed the difference between delete and destroy. In your controller you call
slider.delete
which will just execute a SQL DELETE but will not call any callbacks. That's why in normal cases you want to use destroy instead. It will fetch the object (if necessary), call the callbacks including recursive destroys and only then deletes the object from the database.
See the documentation for delete and destroy for more information.
Ensure that you are calling Slider#destroy in order to trigger callbacks. Slider#delete will simply delete the record without calling them.
class Slider < ActiveRecord::Base
has_one :featured_happening, :dependent => :destroy
end
class FeaturedHappening < ActiveRecord::Base
belongs_to :featured_item
end
If your code was simple like above in which there is simple relationship. Then with the deletion of slider, Feature Happing will be delete with the deletion of slider. But in your case the relationship is polymorphic. In feature happing table in DB the abject_id will be the id of slider or may be some other class. Therefore the dependent :destroy not working. So you should need to delete it with before_destroy callback in which you first retrieve the records and then destroy them