0

I'm trying to prevent an associated record from being deleted if it has associated records of its own. Rails seems to be subverting the :dependent => :restrict option and executing DELETE SQL anyway. What am I missing?

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions
end

class Region < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
end

class UserRegion < ActiveRecord::Base
  belongs_to :user
  belongs_to :region

  has_many :events, :dependent => :restrict
end

class Event < ActiveRecord::Base
  belongs_to :user_region, :readonly => true
  has_one :planner, :through => :user_region, :source => :user
  has_one :region, :through => :user_region
end

Now assume I have two regions [R1 and R2], two users [U1 and U2]. U1 has been assigned to both R1 and R2 [as UR1 and UR2], and U2 has been assigned only to R2 [as UR3]. This gives a total of three UserRegions.

Additionally, I have events E1, E2, and E3, where E1 is in UserRegion UR1, E2 is in UserRegion UR2 and E3 is in UserRegion UR3. This gives U1 two events and U2 one event.

WHAT SHOULD HAPPEN: If I attempt to remove U1's assignment to R2 (i.e. destroy UR2), I should get an error, as U1 has events through UR2.

WHAT IS HAPPENING:

u1 = User.find(1)
urids = u1.regions.map &:id
 => [1,2]
u1.region_ids = [1]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" WHERE "regions"."id" = ? LIMIT 1  [["id", 1]]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" INNER JOIN "user_regions" ON "regions"."id" = "user_regions"."region_id" WHERE "user_regions"."user_id" = 1
 (0.1ms)  begin transaction
SQL (0.3ms)  DELETE FROM "user_regions" WHERE "user_regions"."user_id" = 1 AND "user_regions"."region_id" = 2
 (2.7ms)  commit transaction
 => [1] 

Obviously Rails is not executing the :destroy callbacks on the UserRegion being deleted, because if I attempted to do:

UserRegion.find(2).destroy

I would get an error and the transaction would rollback.

What do I need to do to ensure that assigning Region id's to User doesn't delete UserRegions?

The goal here is that the "edit user" form has a list of check boxes for assigning regions. It should be possible to add regions to a user and remove regions through which the user has no events.

Jarrod Carlson
  • 1,967
  • 4
  • 16
  • 20

2 Answers2

0

Maybe you can search the associated records first and only let the delete line of code execute if the count of them is zero?

def destroy
@hgroup = Hgroup.find(params[:id])
  if 'search for associations' == 0
    @hgroup.destroy
      respond_to do |format|
       format.html { redirect_to hgroups_url }
     end
  else
     'do something else'
  end

end

Try something like that.

OpenCoderX
  • 6,180
  • 7
  • 34
  • 61
  • The User form looks something like (using SimpleForm): <%= f.association :regions, :as => :check_boxes, :collection => Region.all %> Which means the regions are assigned to user as @user.region_ids = [...] Where would I hook into this process to check for existing events? – Jarrod Carlson Jun 24 '12 at 17:15
  • I would do it in the controller in whatever method you use as the 'destroy' action. – OpenCoderX Jun 24 '12 at 17:39
  • I just updated with some code from an app im working on. I didnt test it. – OpenCoderX Jun 24 '12 at 17:46
0

From the ActiveRecord::Associations documentation, in the 'Delete or Destroy?' section:

For has_many, destroy will always call the destroy method of the record(s) being removed so that callbacks are run. However delete will either do the deletion according to the strategy specified by the :dependent option, or if no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify (set the foreign keys to nil), except for has_many :through, where the default strategy is delete_all (delete the join records, without running their callbacks).

So in my User model above, I needed to change this:

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions
end

into this:

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions, :dependent => :destroy
end

This effectively prevents a UserRegion from being deleted if the User still has Events through the UserRegion. An ActiveRecord::DeleteRestrictionError will be raised if this is attempted.

Handling this error at the controller level and setting up my view so this condition is never encountered is, of course, another story.

Jarrod Carlson
  • 1,967
  • 4
  • 16
  • 20