Использует полиморфную ассоциацию с несколькими ассоциациями в одной и той же модели
-
21-09-2019 - |
Вопрос
Мой вопрос по сути такой же, как и этот:Полиморфная ассоциация с несколькими ассоциациями на одной и той же модели
Однако предложенное / принятое решение не работает, как проиллюстрировано комментатором позже.
У меня есть класс фотографий, который используется во всем моем приложении.В посте может быть только одна фотография.Однако я хочу повторно использовать полиморфное отношение, чтобы добавить дополнительную фотографию.
До того , как:
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#secondary_photo, который просто возвращает ту же фотографию, которая прикреплена через ассоциацию фотографий, напримерСообщение #фото === Сообщение#второстепенное фото.Глядя на SQL, он делает WHERE type = "Photo" вместо, скажем, "SecondaryPhoto", как мне бы хотелось...
Мысли?Спасибо!
Решение
Я сделал это в своем проекте.
Хитрость в том, что для фотографий нужен столбец, который будет использоваться в условии 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 достаточно умен, чтобы сделать это.
Другие советы
Рельсы 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
Будущая ссылка для людей, проверяющих этот пост
Этого можно добиться, используя следующий код...
Рельсы 3:
has_one :banner_image, conditions: { attachable_type: 'ThemeBannerAttachment' }, class_name: 'Attachment', foreign_key: 'attachable_id', dependent: :destroy
Рельсы 4:
has_one :banner_image, -> { where attachable_type: 'ThemeBannerAttachment'}, class_name: 'Attachment', dependent: :destroy
Не знаю почему, но в Rails 3 вам необходимо указать значение Foreign_key вместе с условиями и именем класса.Не используйте 'as::attachable», так как при установке полиморфного типа будет автоматически использоваться имя вызывающего класса.
Вышеуказанное относится и к has_many.
Что-то вроде следующего работало для запроса, но назначение адреса пользователю не работало.
Класс пользователя
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
Ни один из предыдущих ответов не помог мне решить эту проблему, поэтому я помещу это здесь, если кто-нибудь еще столкнется с этим.Использование 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!
Надеюсь, это поможет кому-то еще.
Я не использовал его, но я погуглил, просмотрел исходники Rails и думаю, что вы ищете :foreign_type
.Попробуйте и скажите, работает ли это :)
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'
Я думаю, что этот тип в вашем вопросе должен быть Post
вместо Photo
и, соответственно, лучше было бы использовать SecondaryPost
как это было назначено Post
модель.
РЕДАКТИРОВАТЬ:
Ответ выше совершенно неверен. :foreign_type
доступен в полиморфной модели в belongs_to
ассоциация, чтобы указать имя столбца, который содержит тип связанной модели.
Когда я смотрю исходники Rails, эта строка устанавливает этот тип для ассоциации:
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
Как вы можете видеть, он использует base_class.name
чтобы получить имя типа.Насколько я знаю, с этим ничего не поделаешь.
Поэтому я предлагаю добавить один столбец в модель Photo, например: photo_type
.И установите значение 0, если это первая фотография, или установите значение 1, если это вторая фотография.В свои ассоциации добавьте :conditions => {:photo_type => 0}
и :conditions => {:photo_type => 1}
, соответственно.Я знаю, что это не то решение, которое вы ищете, но я не могу найти ничего лучше.Кстати, возможно, лучше было бы просто использовать has_many
ассоциация?
Вам придется обезьяньим образом преобразовать понятие foreign_type в отношение has_one.Это то, что я сделал для has_many.В новом файле .rb в вашей папке инициализаторов я назвал свой add_foreign_type_support.rb Он позволяет вам указать, каким должен быть ваш attachable_type.Пример:есть много фотографий, :имя_класса => "Картинка", :как => прикрепляемое ,: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
Для монгоидный используйте это решение
После обнаружения этой проблемы у нас были тяжелые времена, но мы получили классное решение, которое работает.
Добавьте в свой Gemfile
драгоценный камень 'монгоид-множественный-полиморфный'
И это работает как шарм:
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
Ни одно из этих решений, похоже, не работает на Rails 5.По какой-то причине похоже, что поведение условий ассоциации изменилось.При назначении связанного объекта условия, похоже, не используются во вставке;только при прочтении ассоциации.
Мое решение состояло в том, чтобы переопределить метод установки ассоциации:
has_one :photo, -> { photo_type: 'primary_photo'},
as: 'attachable',
dependent: :destroy
def photo=(photo)
photo.photo_type = 'primary_photo'
super
end
Можете ли вы добавить модель SecondaryPhoto, например:
class SecondaryPhoto < Photo
end
а затем пропустите :class_name из has_one :вторичное_фото?