+1  A: 

Not necessarily a good way to do it, but I did something similar by overriding the method_missing method and then calling my aggregated object. So, it would look something like:

class User
  def method_missing(method_id)
    self.contact_info.send(method_id)
  end
end

Edit 1: Better implementation (I think this will work):

class User
  alias_method :orig_method_missing, :method_missing

  def method_missing(method_id)
    if (self.contact_info.respond_to?(method_id))
      self.contact_info.send(method_id)
    else
      orig_method_missing(method_id)
    end
  end
end

The above has the advantage that all other unknown method calls will get passed correctly.

Topher Fangio
Thank you for this. Your recommendation got me on the right track.
Sean McCleary
+4  A: 

Use delegate:

class User < ActiveRecord::Base
  has_one :contact_info, :as => :contactable

  delegate :name, :name=, :email, :email=, :to => :contact_info
end
amikazmi
That looks like it should work, but I'm getting an error for example.I implemented delegating :email to :contact_info, but it still does not work.>> u = User.create(:email => "[email protected]")ActiveRecord::UnknownAttributeError: unknown attribute: email
Sean McCleary
this was only read delegation, if you want to write also, you need to add setter method "delegate :name, :email, :name=, :email="
amikazmi
Thank you. That got me rolling again. This helped solve my problem. I posted my solution.
Sean McCleary
A: 

I finally got it! Thank you both amikazmi and Topher Fangio. I had to implement both the delegate and method_missing techniques to get this to work.

Here is the total madness that finally ended up working for me! If anybody has suggestions on how to further improve this, I'd love to hear your suggestions.

class User < ActiveRecord::Base
  attr_accessible *([:user_name, :udid, :password, :password_confirmation, :contact_info] + ContactInfo.accessible_attributes.to_a.map {|a| a.to_sym})

  has_one :contact_info, :as => :contactable

  def method_missing(method_id, *args)
    if (!self.respond_to?(method_id) && self.contact_info.respond_to?(method_id))
      self.contact_info.send(method_id, *args)
    elsif (!self.class.respond_to?(method_id) && ContactInfo.respond_to?(method_id))
      ContactInfo.send(method_id, *args)
    else
      super(method_id, *args)
    end
  end

  # delegating attributes seems redundant with the method_missing above, but this secret sauce works.
  ContactInfo.accessible_attributes.to_a.each do |a|
    delegate a.to_sym, "#{a}=".to_sym, :to => :contact_info
  end

  def initialize(*args)
    options = args.extract_options!
    contact_attrs = ContactInfo.accessible_attributes.to_a.map{|a| a.to_sym}
    @ci = ContactInfo.new(options.reject {|k,v| !contact_attrs.include?(k) })
    super(*(args << options.reject { |k,v| contact_attrs.include?(k) }.merge(:contact_info => @ci) ) )
    self.contact_info = @ci
  end

  validates_presence_of :user_name
  validates_uniqueness_of :user_name

  validates_associated  :contact_info

  def after_save
    # automatically save the contact info record for the user after the user has been saved.
    self.contact_info.save!
  end

end

class ContactInfo < ActiveRecord::Base
  set_table_name "contact_info"

  belongs_to :contactable, :polymorphic => true

  validates_presence_of :email
  validates_uniqueness_of :email

  attr_accessible :first_name,
                  :last_name, 
                  :middle_initial, 
                  :title, 
                  :organization_name, 
                  :email, 
                  :email_2, 
                  :twitter_name, 
                  :website_url, 
                  :address_1, 
                  :address_2, 
                  :city, 
                  :state, 
                  :zip, 
                  :phone_work, 
                  :phone_mobile, 
                  :phone_other, 
                  :phone_other_type

  def full_name
    [self.first_name, self.last_name].compact.join(' ')
  end

end
Sean McCleary