Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent orphaned objects, with has_many through associations, in Rails 3

I have a model with "member", "group", "membership" and "user". The data is organized in a tree structure with groups that have members associated to them. Members without any group associations are considered orphaned and is of no use for the application.

When a user destroys a group there should not be left any orphaned members. In other words: a member should be destroyed if and only if the last group association is removed. Preferably this should happen in a single transaction, but the most important aspect is that orphaned object is not accumulated in the database over time.

Only members and groups that are associated with the user should be removed. Groups, members and memberships owned by other users should not be affected at all. (One might argue that a global cleanup method could be run anytime, but I want to isolate destructive operations to only affect the current users objects.)

My question: What is the most efficient and elegant way to implement this functionality in Rails 3? My current implementation, illustrated by the simplified model described here, does not remove a member from the database unless the user manually deletes it (or that the entire user and all his/her data is removed by cascade deletion.)

class User < ActiveRecord::Base
  has_many :groups, :foreign_key => 'owner_id', :dependent => :delete_all
  has_many :members, :foreign_key => 'owner_id', :dependent => :destroy
end

class Member < ActiveRecord::Base
  belongs_to :owner, :class_name => 'User'    
  has_many :memberships, :dependent => :destroy
  has_many :groups, :through => :memberships
end

class Membership < ActiveRecord::Base
  belongs_to :member
  belongs_to :group
end

class Group < ActiveRecord::Base
  belongs_to :owner, :class_name => 'User'
  belongs_to :parent, :class_name => 'Group'    
  has_many :groups, :foreign_key => 'parent_id', :dependent => :destroy
  has_many :memberships, :dependent => :destroy
  has_many :members, :through => :memberships
end
like image 453
Lars Preben Sørsdahl Avatar asked Dec 27 '22 22:12

Lars Preben Sørsdahl


1 Answers

I solved this by adding a callback in the Membership class, which notifies a Member when the membership is destroyed. The member object then destroys itself if it no longer has any group associations.

class Membership < ActiveRecord::Base
  belongs_to :member
  belongs_to :group

  after_destroy :notify_member

  def notify_member
    member.destroy_if_empty_groups
  end
end

class Member < ActiveRecord::Base
  belongs_to :owner, :class_name => 'User'    
  has_many :memberships, :dependent => :destroy
  has_many :groups, :through => :memberships

  def destroy_if_empty_groups
    if groups.count == 0
      self.destroy
    end
  end
end
like image 98
Lars Preben Sørsdahl Avatar answered Jan 31 '23 09:01

Lars Preben Sørsdahl