views:

100

answers:

3

Using ActiveRecord, I have an object, Client, that zero or more Users (i.e. via a has_many association). Client also has a 'primary_contact' attribute that can be manually set, but always has to point to one of the associated users. I.e. primary_contact can only be blank if there are no associated users.

What's the best way to implement Client such that:

a) The first time a user is added to a client, primary_contact is set to point to that user?

b) The primary_contact is always guaranteed to be in the users association, unless all of the users are deleted? (This has two parts: when setting a new primary_contact or removing a user from the association)

In other words, how can I designate and reassign the title of "primary contact" to one of a given client's users? I've tinkered around with numerous filters and validations, but I just can't get it right. Any help would be appreciated.


UPDATE: Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.

+1  A: 

My solution is to do everything in the join model. I think this works correctly on the client transitions to or from zero associations, always guaranteeing a primary contact is designated if there is any existing association. I'd be interested to hear anyone's feedback.


I'm new here, so cannot comment on François below. I can only edit my own entry. His solution presumes user to client is one to many, whereas my solution presumes many to many. I was thinking the user model represented an "agent" or "rep" perhaps, and would surely manage multiple clients. The question is ambiguous in this regard.


class User < ActiveRecord::Base
  has_many :user_clients, :dependent => true
  has_many :clients, :through => :user_client

end

class UserClient < ActiveRecord::Base

  belongs_to :user
  belongs_to :client

  # user_client join table contains :primary column

  after_create :init_primary
  before_destroy :preserve_primary

  def init_primary
    # first association for a client is always primary
    if self.client.user_clients.length == 1 
      self.primary = true
      self.save
    end
  end

  def preserve_primary
    if self.primary
      #unless this is the last association, make soemone else primary
      unless self.client.user_clients.length == 1 
        # there's gotta be a more concise way...
        if self.client.user_clients[0].equal? self
          self.client.user_clients[1].primary = true
        else
          self.client.user_clients[0].primary = true
        end
      end
    end
  end

end

class Client < ActiveRecord::Base
  has_many :user_clients, :dependent => true
  has_many :users, :through => :user_client

end
Walt Gordon Jones
A: 

I would do this using a boolean attribute on users. #has_one can be used to find the first model that has this boolean set to true.

class Client < AR::B
  has_many :users, :dependent => :destroy
  has_one  :primary_contact, :class_name => "User",
                             :conditions => {:primary_contact => true},
                             :dependent  => :destroy
end

class User < AR::B
  belongs_to :client

  after_save    :ensure_only_primary
  before_create :ensure_at_least_one_primary
  after_destroy :select_another_primary

  private
  # We always want one primary contact, so find another one when I'm being
  # deleted
  def select_another_primary
    return unless primary_contact?
    u = self.client.users.first
    u.update_attribute(:primary_contact, true) if u
  end

  def ensure_at_least_one_primary
    return if self.client.users.count(:primary_contact).nonzero?
    self.primary_contact = true
  end

  # We want only 1 primary contact, so if I am the primary contact, all other
  # ones have to be secondary
  def ensure_only_primary
    return unless primary_contact?
    self.client.users.update_all(["primary_contact = ?", false], ["id <> ?", self.id])
  end
end
François Beausoleil
A: 

Though I'm sure there are a myriad of solutions, I ended up having User inform Client when it is being deleted and then using a before_save call in Client to validate (and set, if necessary) its primary_contact. This call is triggered by User just before it is deleted. This doesn't catch all of the edge cases when updating associations, but it's good enough for what I need.

D Carney