Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #3837 (closed enhancement: wontfix)

Opened 4 years ago

Last modified 3 years ago

[PATCH] Add :dependent => :protect to has_many association (like ON DELETE RESTRICT)

Reported by: anonymous Assigned to: David
Priority: normal Milestone: 1.2.4
Component: ActiveRecord Version: 1.0.0
Severity: normal Keywords: dependent, tested
Cc: diego.algorta@gmail.com

Description

Let's say I have:

class Department < ActiveRecord::Base
  has_many :employees
end

class Employee < ActiveRecord::Base
  belongs_to :department
end

If I destroy a department that has employees, the employees department_id becomes invalid and referential integrity brakes. I know there's :dependent => :nullify and :destroy, but there's nothing to stop me of deleting departments unless I specifically deassociate it's employees.

So I added the :protected option.

class Department < ActiveRecord::Base
  has_many :employees, :dependent => :protect
end

It will raise a ReferentialIntegrityProtectionError whenever you try to destroy a Department whose employees.count > 0.

I'm attaching the patch with unit test included.

What do you think? Diego

Attachments

add_dependent_protect_to_has_many.diff (6.2 kB) - added by diego.algorta@gmail.com on 02/15/06 05:15:34.
add_dependent_protect_to_has_many-2.diff (6.2 kB) - added by diego.algorta@gmail.com on 02/18/06 22:59:02.
The same patch with a little performance related change.

Change History

02/15/06 05:15:34 changed by diego.algorta@gmail.com

  • attachment add_dependent_protect_to_has_many.diff added.

02/15/06 14:00:49 changed by diego.algorta@gmail.com

  • keywords set to dependent, tested.

02/18/06 22:55:48 changed by diego.algorta@gmail.com

I've made a little change to the patch to make it more performant. I've changed a ".count" for a ".find(:first)" so it just executes a SELECT * ... LIMIT 1 instead of a SELECT count(*).

It's just faster. I'm uploading it.

02/18/06 22:59:02 changed by diego.algorta@gmail.com

  • attachment add_dependent_protect_to_has_many-2.diff added.

The same patch with a little performance related change.

02/19/06 04:19:32 changed by david

  • keywords changed from dependent, tested to dependent, tested, risky.

I don't understand when this option is needed over nullify or destroy? Could you explain a business scenario that creates the need for an option between "don't depend, so nullify" and "depends, so destroy"?

02/19/06 13:34:41 changed by diego.algorta@gmail.com

I've explained it in the ticket's main description. I don't want AR to let me destroy departments unless there are no employees on them. I know I'll have to explicity destroy (or associate to another department) the relevant employees if I really want to delete a department, but maybe that's a step I want so I CAN'T leave employees without a department by accident.

Let's see if I can make better...

This patch adds "ON DELETE RESTRICT"'s like functionallity to AR. See: http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html

:dependent => true
:dependent => :destroy
:dependent => :delete_all

are escentially like "ON DELETE CASCADE".

:dependent => :nullify

is like "ON DELETE SET NULL".

:dependent => :protect

(maybe I should have named it :restrict) is like "ON DELETE RESTRICT". It just makes sense to me to complete the options to the AR users.

Hope it helps.

Why risky? The spirit of this patch is quite the opposite to risky. It doesn't brake legacy code and it's not a default option.

03/08/06 12:55:48 changed by diego.algorta@gmail.com

  • keywords changed from dependent, tested, risky to dependent, tested, needs_review.
  • summary changed from [PATCH] Add :dependent => :protect to has_many association to [PATCH] Add :dependent => :protect to has_many association (like ON DELETE RESTRICT).
  • milestone set to 1.1.

03/18/06 03:43:25 changed by david

  • keywords changed from dependent, tested, needs_review to dependent, tested.

06/18/06 21:10:28 changed by Tommy Amstrad

To me this is would appear to be an essential feature. Everyday 'ON DELETE RESTRICT' prevents our database users from doing stupid things and deleting parent records before the child records have either been manually deleted, or associated to a new parent.

Diego's example perfectly demonstrates the need ...

"The accounts department has been out-sourced and no longer exist (so delete Accounts department). However the accounts team are still employees of the company and must not be deleted, and must be moved to a new department"

08/01/06 06:38:05 changed by drodrigueztroitino@yahoo.es

I've turned Diego's patch into a plugin so anyone can use such an useful feature without patching their core Rails installation.

Some explanations about the plugin are at http://ruido-blanco.net/blog/rails-dependent-protect-plugin-english and the main repository is at http://svn.ruido-blanco.net/dependent_protect/trunk/

08/05/06 02:57:03 changed by anonymous

dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao dengpao

04/20/07 03:41:33 changed by josh

  • status changed from new to closed.
  • resolution set to wontfix.

What you describe is more of a validation than a association behavior. I agree with David and you should use the plugin to accomplish the task.