Evaluating :dependent => :destroy - ruby-on-rails

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

Related

How does rails manage associations?

I'm still learning Ruby, and get caught up in alot of the 'magic', wanting to better understand what is actually happening, and making sure that I understand what it is doing.
I've got a user, and each user has entries.
In my user class, I have
has_many :entries
and in my entries class I have
belongs_to :user
I was expecting that the entries table would have a column for users, but I'm not seeing that when I 'describe' the database.
How do I know, or how does Rails know which user the entry is connected to? Or do I need to create a field myself to do that?
It seems strange to me that we have all these 'belongs_to', etc. yet it isn't explicit how that connection is made.
This is a common misconception. Associations do not create the database tables for you. Instead, you have to create them yourself. What you need to be careful of, is that an Entry model would have a user_id field, in order for the association to fully work. I truly would not want to advertise or anything, but i have created a blog post that can help you quite a lot i think :
http://www.codercaste.com/2011/02/06/rails-association-in-plain-english-what-i-wish-i-had-known-before-i-started/

rails_acts_as_paranoid to soft delete a record of a many to many relationship

Hi all
I have users and messages, messages can be deleted by both receiver and sender, without affecting the each-other view.
so when the sender deletes the message the receiver still sees it, hope I'm clear.
I wouldd just add two attributes, sender_archived_at and receiver_archived_at, but I rather manage it with rails_acts_as_paranoid, is it possible and how?
Thanks in advance
I had a better look at the plugin, and I'm confident that there is no way (yet) for doing that with acts_as_paranoid
"rails3_acts_as_paranoid" => hides records instead of deleting them, being able to recover them.
With the help of this gem can able to soft delete a record of many to many relationship also.
e.g. Product , Category a famous many to many relation consider
which has habtm's rich association like :through ,then at Product
model level can define 'acts_as_paranoid' and also at join_table's model
'ProductCategory' model.But remeber like :dependent => :destroy is mentioned.
then suppose any product got soft deleted because of dependent :destory 'ProductCategory' join_table 'deleted_at' also sets, and can handle both way.

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.

Creating a dashboard in Rails

Let's say I had an app that was an address book. I'd like to have a page dedicated to a "dashboard". On this page, I'd like to have a running list of the events that happen within the app itself.
Event examples could be:
A user adds a contact.
A user deletes a contact.
A user updates a contact.
What would be the best way to create this type of functionality? Originally I felt that I could do some creative database calls with existing data, but I wouldn't be able to deal with events that deleted data, like when a contact is deleted.
So now I'm thinking it would have to be a separate table that simply stored the events as they occurred. Would this be how most sites accomplish this?
I could go throughout my app, and each time a CRUD operation is performed I could create a new item in the table detailing what happened, but that doesn't seem very DRY.
I supposed my question would be - what's the best way to create the dashboard functionality within an already existing application such as an address book?
Any guidance would be greatly appreciated.
The easiest way to do this is to user Observers in addition to a "logger" table in your database.
Logger
id
model_name
model_id
message
This way you can set up an Observer for all models that you want to log, and do something like this:
after_delete(contact)
Logger.create({:model_name => contact.class.to_s,
:model_id => contact.id,
:message => "Contact was deleted at #{Time.now}"})
end
Now you can log any event in a way you deem fit. Another great addition to this kind of structure is to implement "Logical Deletes", which means you never really delete a record from the table, you simple give it a flag so that it no longer shows up in regular result sets. There's a plugin that does this called acts_as_paranoid.
If you implement both things above, the dashboard can log all important actions, and if you ever need to see what happened or view the data of those events, it's all in the system and can be accessed via the Console (or controllers, if you set them up).
You may want to check out Timeline Fu: http://github.com/jamesgolick/timeline_fu:
class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Person'
fires :new_post, :on => :create,
:actor => :author
end
I've created similar functionality in the past using acts_as_audited.
This helps you track changes to your models, which you can then present to the user.
It basically just tracks the events in a separate table, as you suggested.
You can use Observers in order to handle the events.
Then just store event with the information needed in the database from those Observers.
Here is a quick link to get you started.
user paper_trail plugin, it is awesome!. We modified it though, it is used for all our audit system for complicated release process.

Calling ActiveRecord's #relationship_ids = [1,2,3] saves immediately. Any workarounds?

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

Resources