ActiveRecord - автоматическое объединение данных модели в совокупность - PullRequest
1 голос
/ 08 января 2010

Допустим, у меня есть две таблицы. </p> <pre><code>class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :type, :default => 'User' t.string :user_name, :null => false t.boolean :is_registered, :default => true # ... many more fields end end end class CreateContactInfo < ActiveRecord::Migration def self.up create_table :contact_info do |t| t.integer :resource_id t.string :resource_type t.string :first_name t.string :last_name t.string :middle_initial t.string :title end end end

class ContactInfo < ActiveRecord::Base
  belongs_to :contactable, :polymorphic => true
end

class User < ActiveRecord::Base
  has_one :contact_info, :as => :contactable
  # composed_of :contact_info # ... It would be nice if magics happened here
end

Я хотел бы, чтобы contact_info пользователя автоматически сливался с моим объектом User в качестве атрибутов объекта user без необходимости говорить @ user.contact_info.first_name; вместо этого я бы предпочел написать @ user.first_name.

Причина, по которой я выделяю атрибуты для таблицы contact_info, заключается в том, что это общие атрибуты для нескольких моделей. Вот почему я настраиваю contact_info как полиморфную ассоциацию.

Кто-нибудь знает хороший способ объединить / объединить атрибуты contact_info непосредственно в мою модель пользователя?

Ответы [ 3 ]

8 голосов
/ 09 января 2010

Использовать делегата:

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

  delegate :name, :name=, :email, :email=, :to => :contact_info
end
1 голос
/ 09 января 2010

Я наконец получил это! Спасибо и Амиказми, и Тоферу Фанхио. Мне пришлось реализовать методы делегата и method_missing, чтобы заставить это работать.

Вот полное безумие, которое, в конце концов, сработало для меня! Если у кого-нибудь есть предложения по дальнейшему улучшению, я бы хотел услышать ваши предложения.

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
1 голос
/ 08 января 2010

Не обязательно хороший способ сделать это, но я сделал нечто подобное, переопределив метод method_missing и затем вызвав мой агрегированный объект. Итак, это будет выглядеть примерно так:

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

Редактировать 1: Лучшая реализация (я думаю, это будет работать):

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

Преимущество вышеописанного состоит в том, что все другие неизвестные вызовы методов будут проходить правильно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...