Как лучше всего связать адрес с несколькими моделями в рельсах?

Этот вопрос на SO, похоже, связан с моим вопросом, но я не уверен, что на мой вопрос ответили.

Адрес может принадлежать более чем одной модели (UserProfile и Event) Какой правильный способ реализовать это?

Основные таблицы:

user_profiles(id)
events(id)

Параметры для реализации таблицы адресов:

  • addresses(id,user_profile_id,event_id)
    Этот подход кажется клонистым, поскольку, если завтра адрес должен принадлежать еще одной модели, я должен добавить это поле id.
    Кроме того, я еще не знаю, но добавление нового поля id может вызвать некоторый код сломать также?

  • addresses(id,model_type,model_id)
    Это полиморфно, правильно. Я не знаю почему, но я так опасаюсь этого?

  • Какой-нибудь другой способ сделать это?

Примечание:

Я мог бы сделать таблицы такими, я полагаю:

user_profiles(id,address_id)
events(id,address_id)

Но это означает, что тот же address_id может принадлежать к разным моделям. Я полагаю, что это не должно быть так, потому что, например, скажем, что адрес для события нужно изменить, но он не должен влиять на адрес user_profile. Так что это будет что-то вроде этого (что, я думаю, неверно):

@current_user_profile.address = some_new_address
#this would have changed the address for both the user_profile *and* the event
@current_user_profile.save 

Ответ 1

Один из способов - стандартный полиморфизм Rails:

class Address
  belongs_to :addressable, :polymorphic => true
end

class UserProfile
  has_one address, :as => :addressable
end

class Event
  has_one address, :as => :addressable
end

Это может быть ошеломляющее чувство, которое у вас есть об этом, заключается в том, что вы не можете создать ограничение уровня db с полиморфными отношениями в стиле Rails. Альтернатива (предложенная Дэн Чак в Enterprise Rails) похожа на ваш вариант # 1, где вы действительно создаете отдельное поле id для каждого типа отношения. Это оставляет неиспользуемые поля, но также допускает ограничения. Я могу видеть аргумент для обоих, но сообщество Rails некоторое время использует AR-полиморфизм с очевидным успехом. Я использую его без колебаний. Но если это вас задевает, вы можете использовать подход Чак. Это намного больше работы.:)

EDIT: @Slick86, миграция будет выглядеть примерно так:

class CreateAddresses < ActiveRecord::Migration
  def change
    create_table :addresses do |t|
      t.integer :addressable_id
      t.string :addressable_type
    end
  end
end

Ответ 2

Вы пропустили один из вариантов: введите класс, который содержит общее поведение, и добавьте поля во все таблицы. Для управления данными используйте скомпилированный агрегат.

class Address
  attr_accessor :line1, :line2, :city, :state, :zip

  def initialize(line1, line2, city, state, zip)
    @line1 = line1
  end
end

class UserProfile < ActiveRecord::Base
  composed_of :address, :mapping => %w(line1 line2 city state zip)
end

class Event < ActiveRecord::Base
  composed_of :address, :mapping => %w(line1 line2 city state zip)
end

См. # created_of в документах API Ruby on Rails.