Prevent object destruction except by parent - ruby-on-rails

Ruby 2.1.2 and Rails 4: I have a parent object and a child object. I have a before_destroy callback for the child object that may prevent its destruction based on a flag. However, I also need its parent to be able to destroy it via a dependent: :destroy relationship.
How can I check the source of its destruction in my validation?
I found marked_for_destruction? and a host of related questions here, but none seem concerned with the before_destroy callback, which runs before the object (or even its parent) are marked for destruction. I've been prying through what's accessible in the callback for a while now and can't seem to find anything.
I could obviously go with dependent: :delete instead, although that seems like it misses the point. I'm sure I could come up with something else like doing a before_destroy on the parent, and then calling a monkey-patched destroy method with some arguments or some such thing, but it also seems to miss the point.
Any suggestions? Is there some property on the parent that I'm missing, or a way to trace the destroy call's source or something? Thanks in advance!

Related

How to avoid a circular loop

I think I'm being dense here because I keep getting a stack too deep error...
I have a Child and a Parent relational objects. I want 2 things to happen:
if you try to update the Child, you cannot update its status_id to 1 unless it has a Parent association
if you create a Parent and then attach it to the Child, then the Child's status should be auto-set to 1.
Here's how the Parent association gets added:
parent = Parent.new
if parent.save
child.update_attributes(parent_id:1)
end
I have these callbacks on the Child model:
validate :mark_complete
after_update :set_complete
# this callback is here because there is a way to update the Child model attributes
def mark_complete
if self.status_id == 1 && self.parent.blank?
errors[:base] << ""
end
end
def set_complete
if self.logistic.present?
self.update_attribute(:status_id, 1)
end
end
The code above is actually not that efficient because it's 2 db hits when ideally it would be 1, done all at once. But I find it too brain draining to figure out why... I'm not sure why it's not even working, and therefore can't even begin to think about making this a singular db transaction.
EXAMPLE
Hopefully this helps clarify. Imagine a Charge model and an Item model. Each Item has a Charge. The Item also has an attribute paid. Two things:
If you update the Item, you cannot update the paid to true until the Item has been associated with a Charge object
If you link a Charge object to a Item by updating the charge_id attribute on the Item, then code should save you time and auto set the paid as true
There's a lot that I find confusing here, but it seems to me that you call :set_complete after_update and within set_complete you are updating attributes, thus you seem to have a perpetual loop there. There might be other loops that I can't see but that one stands out to me.
One way to avoid a circularly recursive situation like this is to provide a flag as a parameter (or otherwise) that will stop the loop from continuing.
In this case, (though I am not sure about the case entirely) I think you could provide a flag indicating the origin of the call. If the origin of the update is a charge being attached, then pass a flag that will stop the check from happening or modify it to keep the loop from happening. Perhaps a secondary set of logic is in order for such a case?
I faced a stack level too deep problem some time back when working with ActiveRecord callbacks.
In my case the problem was with update_attribute after the update goes through the callback i.e. set_complete in your case is called again in which the update_attribute is triggered again in turn and this repeats endlessly.
I got around that by using update_column instead which does not trigger any callbacks or validations however setting a flag is what was advised more often online.
At this point I do not have an answer for reducing your database write operations, and will add to this answer if I can think of anything.
Hope this helps

What's a clean way to test that no callbacks are registered on an ActiveRecord object?

I have an object which for reasons of data scaling, needs to call Object.where(x=y).delete_all. Using destroy_all is too time consuming.
However as a result of that I want to enforce that no dev accidentally registers an after_destroy callback or even a dependent: destroy relationship because they'll both be ignored during the delete_all process.
What would be the best way in RSpec to test that after_destroy a model receives NO callbacks?
I'd like to achieve something along these lines:
it "should not have any registered after_destroy callbacks" do
o = MyObject.new
o.destroy
expect(o).to_not have_received('*')
end
Possible?
I think that approach is doomed: many methods internal to Active Record are called during a call to destroy, and you'd have to sort those out from your methods, or ones defined by callbacks (the bad methods won't necessarily have an obvious name, eg if they use the block form of before/after destroy).
You can however directly inspect the set of callbacks:
MyObject._destroy_callbacks
and check whether it is empty.
You can check what options have been set on associations more explicitly:
MyObject.reflect_on_all_associations.any? {|reflection| reflection.options[:dependent] == :destroy}
but these are implemented using callbacks so should show up in _destroy_callbacks

Rails: Object destroy performance

In my rails app. I have my base model - User. There are lot of associated objects to the user.
Class User
has_many :contents, dependent: destroy
has_many :messages, dependent: destroy
has_many :ratings, dependent: destroy
has_many :groups, dependent: destroy
...
end
When I want to remove user from my system (destroy user object), it takes about a minute to destroy all its associated objects. What is the best way to handle such cases?
One way that comes to my mind is:
Destroying in delayed_job:
But till the point user object gets destroyed in delayed job, this user should not be visible for others. Handle this case by having a flag say - deleted in user model and not fetching in results. But I use sphinx as well, and needs to make sure this user does not come up in sphinx results as well.
Is there a better way to handle such cases?
The challenge is that, as you probably already know, the .destroy method will load each of the children objects and then call their .destroy methods.
The value in this is that any callbacks on the children are evaluated before doing the final destroy. So if a child needs to clear up anything elsewhere then it will do so. Also if the dependent objects throw an error during the destroy method, the entire destroy operation will rollback and you won't end up with some half-dead object limping around.
.delete will destroy the objects without loading them into memory or performing their callbacks. However (obviously) it won't perform their callbacks.
If you want to speed things up you could either simply do dependent: :delete as Octopus-Paul suggests. This will be fine however it won't destroy dependents on those objects, so for instance if groups had messages associated with them or perhaps ratings had comments associated with them, none of those will be destroyed.
To ensure that all downstream dependents get destroyed and any necessary callbacks are honoured I think the best you can do is to write a custom before_destroy method which does all clearing up but uses .delete and .delete_all in order to speed things up.
This will create legacy issues in that someone writing code downstream won't necessarily anticipate your method however you can judge the risk of that. The alternative (as you say) is to use a flag and do the job asynchronously. I'd imagine that this has fewer risks in the future but may be more expensive to implement today.
:dependent
Controls what happens to the associated object when its owner is destroyed:
:destroy causes the associated object to also be destroyed
:delete causes the associated object to be deleted directly from the database (so callbacks will not execute)
Delete will be much faster because it will simply run a database query for each association of the deleted user.
Find more options here: http://guides.rubyonrails.org/association_basics.html#options-for-has-one-dependent

What is the best way to override Rails ActiveRecord destroy behavior?

I have an application where I would like to override the behavior of destroy for many of my models. The use case is that users may have a legitimate need to delete a particular record, but actually deleting the row from the database would destroy referential integrity that affects other related models. For example, a user of the system may want to delete a customer with whom they no longer do business, but transactions with that customer need to be maintained.
It seems I have at least two options:
Duplicate data into the necessarily models effectively denormalizing my data model so that deleted records won't affect related data.
Override the "destroy" behavior of ActiveRecord to do something like set a flag indicating the user "deleted" the record and use this flag to hide the record.
Am I missing a better way?
Option 1 seems like a horrible idea to me, though I'd love to hear arguments to the contrary.
Option 2 seems somewhat Rails-ish but I'm wondering the best way to handle it. Should I create my own parent class that inherits from ActiveRecord::Base, override the destroy method there, then inherit from that class in the models where I want this behavior? Should I also override finder behavior so records marked as deleted aren't returned by default?
If I did this, how would I handle dynamic finders? What about named scopes?
If you're not actually interested in seeing those records again, but only care that the children still exist when the parent is destroyed, the job is simple: add :dependent => :nullify to the has_many call to set references to the parent to NULL automatically upon destruction, and teach the view to deal with that reference being missing. However, this only works if you're okay with not ever seeing the row again, i.e. viewing those transactions shows "[NO LONGER EXISTS]" under company name.
If you do want to see that data again, it sounds like what you want has nothing to do with actually destroying records, which means that you will never need to refer to them again. Hiding seems to be the way to go.
Instead of overriding destroy, since you're not actually destroying the record, it seems significantly simpler to put your behavior in a hide method that triggers a flag, as you suggested.
From there, whenever you want to list these records and only include visible records, one simple solution is to include a visible scope that doesn't include hidden records, and not include it when you want to find that specific, hidden record again. Another path is to use default_scope to hide hidden records and use Model.with_exclusive_scope { find(id) } to pull up a hidden record, but I'd recommend against it, since it could be a serious gotcha for an incoming developer, and fundamentally changes what Model.all returns to not at all reflect what the method call suggests.
I understand the desire to make the controllers look like they're doing things the Rails way, but when you're not really doing things the Rails way, it's best to be explicit about it, especially when it's really not that much of a pain to do so.
I wrote a plugin for this exact purpose, called paranoia. I "borrowed" the idea from acts_as_paranoid and basically re-wrote AAP using much less code.
When you call destroy on a record, it doesn't actually delete it. Instead, it will set a deleted_at column in your database to the current time.
The README on the GitHub page should be helpful for installation & usage. If it isn't, then let me know and I'll see if I can fix that for you.

Evaluating :dependent => :destroy

In Rails 2.2.2 (ruby 1.8.7-p72), I'd like to evaluate the impact of destroying an object before actually doing it. I.e. I would like to be able to generate a list of all objects that will be affected by :dependent => :destroy (via an object's associations). The real problem I'm trying to solve is to give a user a list of everything that will be deleted and having them confirm the action.
Can anyone recommend a good way to go about this? I've just started looking into ActiveRecord::Associations, but I haven't made much headway.
Update: In my particular case, I've got various levels of objects (A --> B --> C).
This should help get you started... Obviously you'll have to customize it but this lists all association names that are dependent destroy on the class BlogEntry:
BlogEntry.reflect_on_all_associations.map do |association|
if association.options[:dependent] == :destroy
# do something here...
association.name
end
end.compact
=> [:taggings, :comments]
Just manually maintain a list of associated object with dependent destroy (probably a go thing to do anyway) and then have named_scopes for each to pull in the included objects to display.
I'd say that as mentioned have a way of displaying affected records to the user, then have two buttons/links, one that is a delete, maybe with a confirm alert for the user which asks if they have checked the other link which is a list of all records they will be affecting.
Then if you want to be really sure you could also do a soft delete by marking them as deleted at in the database instead of actually deleting them which may well come in handy, I don't know how you would handle that on the automatic dependent delete, maybe with acts_as_paranoid, or some kind of self rolled version with a callback on the parent model.
Recently I wrote a simple Rails plugin that solves this problem.
Check it out on github: http://github.com/murbanski/affected_on_destroy/tree

Resources