delete_all does not work while each do delete does - ruby-on-rails

Intended functionality - delete all linked asset_items when an asset_line_item is deleted. (without using destroy, destroy_all). I am using postgres
With the following model:
class AssetLineItem < PurchaseLineItem
has_many :asset_items
...
after_destroy :destroy_cleanup
private
def destroy_cleanup
asset_items.delete_all
end
end
This results in the asset_items remaining, however all of their asset_line_item columns are set to null.
def destroy_cleanup
asset_items.each do |asset_item|
asset_item.delete
end
end
replacing delete_all with the loop above however has the intended result of delete all associated asset_items.
Although I have working code, I'm curious what could cause delete_all to act in this way?

Calling just delete_all on association just nullifies the reference. It is the same as delete_all(:nullify):
pry(main)> Booking.last.passengers.delete_all
Booking Load (0.6ms) SELECT `bookings`.* FROM `bookings` ORDER BY `bookings`.`id` DESC LIMIT 1
SQL (2.8ms) UPDATE `passengers` SET `passengers`.`booking_id` = NULL WHERE `passengers`.`booking_id` = 157
=> nil
You need to call delete_all(:delete_all) to actually delete associated records.
Here is docs.
Or to get desired effect you can add following line to your AssetLineItem model:
has_many :asset_items, dependent: :destroy
as lakhvir kumar mentioned.
Also your destroy_cleanup callback could be refactored to:
def destroy_cleanup
asset_items.map(&:delete)
end
Here is some good links to the topic:
delete_all vs destroy_all?
Rails :dependent => :destroy VS :dependent => :delete_all

use has_many :asset_items dependent: :destroy

Related

What is the best way to check multiple has_many associations?

I want to check if any of a set of has_many associations of a Ruby Class has, at least, one item.
Currently the method in_use? is written this way:
class Venue < ApplicationRecord
has_many :destination_day_items
has_many :user_bookmarks
has_many :attachments
has_many :notes
has_many :notifications
has_many :expenses
def in_use?
destination_day_items.any? ||
user_bookmarks.any? ||
attachments.any? ||
notes.any? ||
notifications.any? ||
expenses.any?
end
end
I think that adding a local variable may prevent a lot of repetead calls if this method is called a few times.
def in_use?
#in_use ||= destination_day_items.any? ||
user_bookmarks.any? ||
(...)
#in_use
end
But I still feel it's not the best approach.
My question is: Does anyone know a better idea on how to implement this using "The RoR Way"?
At first this was intendend to check if a Venue is associated to any other of these classes (Bookmarks, Notes, Attachments...) and validate if it's safe to delete it
You can use option :restrict_with_error. This option causes an error to be added to the owner if there is an associated object
class Venue < ApplicationRecord
has_many :destination_day_items, dependent: :restrict_with_error
has_many :user_bookmarks, dependent: :restrict_with_error
has_many :attachments, dependent: :restrict_with_error
has_many :notes, dependent: :restrict_with_error
has_many :notifications, dependent: :restrict_with_error
has_many :expenses, dependent: :restrict_with_error
end
Let's assume venue has some destination day item and we try to destroy it
venue.destroy
Output will something like this:
BEGIN
DestinationDayItem Exists? SELECT 1 AS one FROM "destination_day_items" WHERE "destination_day_items"."venue_id" = $1 LIMIT $2 [["venue_id", 1], ["LIMIT", 1]]
ROLLBACK
=> false
venue.errors[:base]
# => ["Cannot delete record because dependent destination day item exist"]
So you can show this message to the user if you need
Another option -- dependent: :restrict_with_exception. It causes an ActiveRecord::DeleteRestrictionError exception to be raised if there is an associated record
These options work when you apply destroy method and don't work when delete
As they say, there's only two hard problems in computer science: "Cache invalidation, naming things, and off-by-one errors". The cached result can fall out of date if any relationships are added or removed during the life of the object.
Also, associations are cached, so there isn't much use in caching the result. destination_day_items will only query the database once during the life of the object. However, the cache is not always properly invalidated if the relationships change.
You could add a counter_cache to all the associations, then it only has to query the cache number, but see above.
Instead of a query for each association, you can increase performance by doing it in a single query.
def in_use?
left_joins(:destination_day_items)
.left_joins(:user_bookmarks)
...
.where.not(destination_day_items: { venue_id: nil })
.or( UserBookmarks.where.not(venue_id: nil )
.or( ... )
.exists?
end
The upside is this query will always get the correct result. The downside is it will always run the query. You could cache the result, but see above.
The real advantage is this can be turned into a scope to search for all in-use venues efficiently.
scope :left_join_all_associations(
left_joins(:destination_day_items)
.left_joins(:user_bookmarks)
...
)
scope :in_use, -> {
left_join_all_associations
.where.not(destination_day_items: { venue_id: nil })
.or( UserBookmarks.where.not(venue_id: nil )
.or( ... )
}
And you can query for those which are not in use with a left excluding join.
scope :not_in_use, -> {
left_join_all_associations
.where(
destination_day_items: { venue_id: nil },
user_bookmarks: { venue_id; nil },
...
)
}
I'm not 100% sure I got the Rails queries right, so here's a SQL demonstration.
def in_use?
# define a relations array
relations = Venue.reflect_on_all_associations(:has_many).map(&:name).map(&:to_s)
# check condition
relations.any? { |association| public_send(association).any? }
end

Rails: Delete records created before adding dependant: :destroy

Is there any efficient way to delete all associated records which where created before adding dependant: :destroy ??
Class User
end
Class Post
belongs_to :user, dependent: :destroy
end
Previous records which were created before adding this dependent: :destroy are still present and have user_id present.
eg.
Post.first.user_id = 1
But User.first which has id of 1 is already destroyed before adding the dependent: : destroy.
How do i find and delete these Post records???
Post.where("NOT EXISTS(select 1 from #{User.table_name} where #{Post.table_name}.user_id=#{User.table_name}.id)").delete_all
This should delete all the posts whose associated user no longer exists in the db.
If you want to trigger the callbacks for each Post before deleting them use destroy_all instead.
See: delete_all vs destroy_all? for the difference.

:dependent => :destroy on postgresql uuid

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.

Rails :dependent => destroy, want to call another action instead of destroy

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

How do I prevent deletion of parent if it has child records?

I have looked through the Ruby on Rails guides and I can't seem to figure out how to prevent someone from deleting a Parent record if it has Children. For example. If my database has CUSTOMERS and each customer can have multiple ORDERS, I want to prevent someone from deleting a customer if it has any orders in the database. They should only be able to delete a customer if it has no orders.
Is there a way when defining the association between models to enforce this behavior?
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict # raises ActiveRecord::DeleteRestrictionError
Edit: as of Rails 4.1, :restrict is not a valid option, and instead you should use either :restrict_with_error or :restrict_with_exception
Eg.:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict_with_error
You could do this in a callback:
class Customer < ActiveRecord::Base
has_many :orders
before_destroy :check_for_orders
private
def check_for_orders
if orders.count > 0
errors.add_to_base("cannot delete customer while orders exist")
return false
end
end
end
EDIT
see this answer for a better way to do this.
Try using filters to hook in custom code during request processing.
One possibility would be to avoid providing your users a link to deletion in this scenario.
link_to_unless !#customer.orders.empty?
Another way would be to handle this in your controller:
if !#customer.orders.empty?
flash[:notice] = "Cannot delete a customer with orders"
render :action => :some_action
end
Or, as Joe suggests, before_filters could work well here and would probably be a much more DRY way of doing this, especially if you want this type of behavior for more models than just Customer.

Resources