Рельсовая полиморфная ассоциация с несколькими ассоциациями на одной модели - PullRequest
52 голосов
/ 22 марта 2010

Мой вопрос по сути такой же, как этот: Полиморфная ассоциация с несколькими ассоциациями на одной модели

Тем не менее, предлагаемое / принятое решение не работает, как показано позже комментатором.

У меня есть класс Photo, который используется во всем приложении. Сообщение может иметь одну фотографию. Однако я хочу повторно использовать полиморфные отношения, чтобы добавить дополнительную фотографию.

До:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
end

Желаемая:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo,           :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, :as => :attachable, :dependent => :destroy
end

Однако это не удается, так как не удается найти класс «SecondaryPhoto». Исходя из того, что я могу сказать из этого другого потока, я бы хотел сделать:

   has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy

За исключением вызова Post # second_photo просто возвращает ту же фотографию, которая прикреплена через ассоциацию фотографий, например, Сообщение # фото === Сообщение # вторичное_фото. Глядя на SQL, он делает WHERE type = "Photo" вместо, скажем, "SecondaryPhoto", как мне бы хотелось ...

Мысли? Спасибо!

Ответы [ 10 ]

69 голосов
/ 20 июня 2010

Я сделал это в своем проекте.

Хитрость в том, что фотографиям нужен столбец, который будет использоваться в условии has_one, чтобы различать первичные и вторичные фотографии.Обратите внимание на то, что здесь происходит в :conditions.

has_one :photo, :as => 'attachable', 
        :conditions => {:photo_type => 'primary_photo'}, :dependent => :destroy

has_one :secondary_photo, :class_name => 'Photo', :as => 'attachable',
        :conditions => {:photo_type => 'secondary_photo'}, :dependent => :destroy

Прелесть этого подхода в том, что при создании фотографий с использованием @post.build_photo тип photo_type автоматически будет предварительно заполнен соответствующим типом, например 'primary_photo.ActiveRecord достаточно умен, чтобы сделать это.

20 голосов
/ 01 февраля 2015

Рельсы 4.2 +

class Photo
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, -> { where attachable_type: "SecondaryPhoto"},
     class_name: Photo, foreign_key: :attachable_id,
     foreign_type: :attachable_type, dependent: :destroy
end

Вы должны предоставить foreign_key согласно .... able'ness или Rails запросит столбец post_id в таблице фотографий Столбец Attachable_type заполняется магией Rails как SecondaryPhoto

5 голосов
/ 22 октября 2014

Что-то вроде следующего работало для запросов, но назначение от пользователя к адресу не работало

Класс пользователя

has_many :addresses, as: :address_holder
has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" },
       class_name: "Address", foreign_key: "address_holder_id"

Адрес класса

belongs_to :address_holder, polymorphic: true
5 голосов
/ 13 октября 2014

Будущие ссылки для людей, проверяющих этот пост

Этого можно достичь, используя следующий код ...

Rails 3:

has_one :banner_image, conditions: { attachable_type: 'ThemeBannerAttachment' }, class_name: 'Attachment', foreign_key: 'attachable_id', dependent: :destroy

Rails 4:

has_one :banner_image, -> { where attachable_type: 'ThemeBannerAttachment'}, class_name: 'Attachment', dependent: :destroy

Не знаю, почему, но в Rails 3 вам необходимо указать значение foreign_key вместе с условиями и class_name.Не используйте «as:: attachable», так как это будет автоматически использовать имя вызывающего класса при установке полиморфного типа.

Вышесказанное относится и к has_many.

3 голосов
/ 04 апреля 2017

Ни один из предыдущих ответов не помог мне решить эту проблему, поэтому я поставлю это здесь, если кто-нибудь еще столкнется с этим. Использование Rails 4.2 +.

Создайте миграцию (если у вас уже есть таблица адресов):

class AddPolymorphicColumnsToAddress < ActiveRecord::Migration
  def change
    add_column :addresses, :addressable_type, :string, index: true
    add_column :addresses, :addressable_id, :integer, index: true
    add_column :addresses, :addressable_scope, :string, index: true
  end
end

Настройка вашей полиморфной ассоциации:

class Address < ActiveRecord::Base
  belongs_to :addressable, polymorphic: true
end

Установите класс, из которого будет вызываться ассоциация:

class Order < ActiveRecord::Base
  has_one :bill_address, -> { where(addressable_scope: :bill_address) }, as: :addressable,  class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :bill_address, allow_destroy: true

  has_one :ship_address, -> { where(addressable_scope: :ship_address) }, as: :addressable, class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :ship_address, allow_destroy: true
end

Хитрость в том, что вы должны вызывать метод сборки для экземпляра Order, иначе столбец scope не будет заполнен.

Так что это НЕ работает:

address = {attr1: "value"... etc...}
order = Order.new(bill_address: address)
order.save!

Однако, это РАБОТАЕТ.

address = {attr1: "value"... etc...}
order = Order.new
order.build_bill_address(address)
order.save!

Надеюсь, это поможет кому-то еще.

3 голосов
/ 22 марта 2010

Я не использовал его, но я погуглил и посмотрел на источники Rails, и я думаю, что вы ищете :foreign_type. Попробуйте и скажите, работает ли оно:)

has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'

Я думаю, что тип в вашем вопросе должен быть Post вместо Photo, и, соответственно, было бы лучше использовать SecondaryPost, поскольку он назначен Post модели.

EDIT:

Приведенный выше ответ совершенно неверен. :foreign_type доступен в полиморфной модели в ассоциации belongs_to для указания имени столбца, который содержит тип связанной модели.

Как я смотрю в источниках Rails, эта строка устанавливает этот тип для ассоциации:

dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]

Как видите, для получения имени типа используется base_class.name. Насколько я знаю, с этим ничего не поделаешь.

Поэтому я предлагаю добавить один столбец к модели фотографий, например: photo_type. И установите его на 0, если это первая фотография, или установите на 1, если это вторая фотография. В ваши ассоциации добавьте :conditions => {:photo_type => 0} и :conditions => {:photo_type => 1}, соответственно. Я знаю, что это не решение, которое вы ищете, но я не могу найти ничего лучше. Кстати, может быть, было бы лучше просто использовать has_many ассоциацию?

2 голосов
/ 10 июня 2010

Вы должны будете обезьяной соединить понятие foreign_type с отношением has_one. Это то, что я сделал для has_many. В новом файле .rb в папке инициализаторов я назвал мой add_foreign_type_support.rb Это позволяет вам указать, каким должен быть ваш тип вложения. Пример: has_many photo,: class_name => "Picture",: as => присоединяемый,: foreign_type => 'Pic'

module ActiveRecord
  module Associations
    class HasManyAssociation < AssociationCollection #:nodoc:
      protected
        def construct_sql
          case
            when @reflection.options[:finder_sql]
              @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
           when @reflection.options[:as]
              resource_type = @reflection.options[:foreign_type].to_s.camelize || @owner.class.base_class.name.to_s
              @finder_sql =  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND "
              @finder_sql += "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(resource_type)}"
              else
                @finder_sql += ")"
              end
              @finder_sql << " AND (#{conditions})" if conditions

            else
              @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
              @finder_sql << " AND (#{conditions})" if conditions
          end

          if @reflection.options[:counter_sql]
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          elsif @reflection.options[:finder_sql]
            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
            @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          else
            @counter_sql = @finder_sql
          end
        end
    end
  end
end
# Add foreign_type to options list
module ActiveRecord
  module Associations # :nodoc:
     module ClassMethods
      private
        mattr_accessor :valid_keys_for_has_many_association
        @@valid_keys_for_has_many_association = [
          :class_name, :table_name, :foreign_key, :primary_key, 
          :dependent,
          :select, :conditions, :include, :order, :group, :having, :limit, :offset,
          :as, :foreign_type, :through, :source, :source_type,
          :uniq,
          :finder_sql, :counter_sql,
          :before_add, :after_add, :before_remove, :after_remove,
          :extend, :readonly,
          :validate, :inverse_of
        ]

    end
  end
1 голос
/ 26 октября 2017

Похоже, что ни одно из этих решений не работает на Rails 5. По какой-то причине похоже, что поведение в условиях ассоциации изменилось. При назначении связанного объекта условия не используются во вставке; только при прочтении ассоциации.

Мое решение состояло в том, чтобы переопределить метод установки для ассоциации:

has_one :photo, -> { photo_type: 'primary_photo'},
        as: 'attachable',
        dependent: :destroy

def photo=(photo)
  photo.photo_type = 'primary_photo'
  super
end
1 голос
/ 14 июня 2015

Для mongoid используйте это решение

Были трудные времена после обнаружения этой проблемы, но было крутое решение, которое работает

Добавить в свой Gemfile

драгоценный камень 'mongoid-множественно-полиморфный'

И это работает как шарм:

  class Resource

  has_one :icon, as: :assetable, class_name: 'Asset', dependent: :destroy, autosave: true
  has_one :preview, as: :assetable, class_name: 'Asset', dependent: :destroy, autosave: true

  end
0 голосов
/ 22 марта 2010

Можете ли вы добавить модель SecondaryPhoto, например:

class SecondaryPhoto < Photo
end

и затем пропустите: имя_класса из has_one: second_photo?

...