Ma question est essentiellement la même que celle-ci: Association polymorphe avec plusieurs associations sur le même modèle
Cependant, la solution proposée/acceptée ne fonctionne pas, comme illustré par un commentateur plus tard.
J'ai un cours photo qui est utilisé partout dans mon application. Un message peut avoir une seule photo. Cependant, je veux réutiliser la relation polymorphe pour ajouter une photo secondaire.
Avant:
class Photo
belongs_to :attachable, :polymorphic => true
end
class Post
has_one :photo, :as => :attachable, :dependent => :destroy
end
Voulu:
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
Cependant, cela échoue car il ne peut pas trouver la classe "SecondaryPhoto". Sur la base de ce que je pouvais dire de cet autre fil, je voudrais faire:
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy
Sauf en appelant Post # secondary_photo renvoie simplement la même photo qui est jointe via l'association Photo, par ex. Poster # photo === Poster # secondaire_photo. En regardant le SQL, il fait O type type = "Photo" au lieu de, disons, "SecondaryPhoto" comme je voudrais ...
Pensées? Merci!
Je l'ai fait dans mon projet.
L'astuce est que les photos ont besoin d'une colonne qui sera utilisée dans une condition has_one pour faire la distinction entre les photos primaires et secondaires. Faites attention à ce qui se passe dans :conditions
ici.
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
La beauté de cette approche est que lorsque vous créez des photos à l'aide de @post.build_photo
, le photo_type sera automatiquement prérempli avec le type correspondant, comme 'primary_photo'. ActiveRecord est suffisamment intelligent pour cela.
future référence pour les personnes vérifiant ce message
Ceci peut être réalisé en utilisant le code suivant ...
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
Je ne sais pas pourquoi, mais dans Rails 3, vous devez fournir une valeur foreign_key à côté des conditions et class_name. N'utilisez pas 'as:: attachable' car cela utilisera automatiquement le nom de la classe appelante lorsque définition du type polymorphe.
Ce qui précède s'applique également à has_many.
Quelque chose comme le suivant a fonctionné pour l'interrogation, mais l'attribution de l'utilisateur à l'adresse n'a pas fonctionné
Classe d'utilisateurs
has_many :addresses, as: :address_holder
has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" },
class_name: "Address", foreign_key: "address_holder_id"
Classe d'adresse
belongs_to :address_holder, polymorphic: true
Aucune des réponses précédentes ne m'a aidé à résoudre ce problème, je vais donc mettre cela ici au cas où quelqu'un d'autre se heurterait à cela. Utilisation de Rails 4.2 +.
Créez la migration (en supposant que vous disposez déjà d'une table Adresses):
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
Configurez votre association polymorphe:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
Configurez la classe d'où l'association sera appelée:
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
L'astuce est que vous devez appeler la méthode de génération sur l'instance Order
ou la colonne scope
ne sera pas remplie.
Donc cela ne fonctionne PAS:
address = {attr1: "value"... etc...}
order = Order.new(bill_address: address)
order.save!
Cependant, cela FONCTIONNE.
address = {attr1: "value"... etc...}
order = Order.new
order.build_bill_address(address)
order.save!
J'espère que cela aide quelqu'un d'autre.
Je ne l'ai pas utilisé, mais j'ai fait une recherche sur Google et j'ai examiné les sources de Rails et je pense que ce que vous recherchez est :foreign_type
. Essayez-le et dites si cela fonctionne :)
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'
Je pense que le type de votre question devrait être Post
au lieu de Photo
et, respectivement, il serait préférable d'utiliser SecondaryPost
comme il est attribué au modèle Post
.
ÉDITER:
La réponse ci-dessus est complètement fausse. :foreign_type
est disponible en modèle polymorphe dans belongs_to
association pour spécifier le nom de la colonne qui contient le type de modèle associé.
Comme je regarde dans Rails sources, cette ligne définit ce type d'association:
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
Comme vous pouvez le voir, il utilise base_class.name
pour obtenir le nom du type. Pour autant que je sache, vous ne pouvez rien en faire.
Donc, ma suggestion est d'ajouter une colonne au modèle Photo, par exemple: photo_type
. Et définissez-le sur 0 s'il s'agit de la première photo, ou définissez-le sur 1 s'il s'agit de la deuxième photo. Dans vos associations, ajoutez :conditions => {:photo_type => 0}
et :conditions => {:photo_type => 1}
, respectivement. Je sais que ce n'est pas une solution que vous recherchez, mais je ne trouve rien de mieux. Soit dit en passant, il serait peut-être préférable d'utiliser simplement has_many
association?
Vous allez devoir patcher la notion de type_étranger dans la relation has_one. C'est ce que j'ai fait pour has_many. Dans un nouveau fichier .rb dans votre dossier d'initialiseurs, j'ai appelé le mien add_foreign_type_support.rb Il vous permet de spécifier quel doit être votre attachable_type. Exemple: has_many photo,: class_name => "Picture",: as => attachable,: 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
A eu des moments difficiles après avoir découvert ce problème, mais a obtenu une solution intéressante qui fonctionne
Ajouter à votre Gemfile
gemme 'mongoid-multiple-polymorphic'
Et cela fonctionne comme un charme:
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
Aucune de ces solutions ne semble fonctionner sur Rails 5. Pour une raison quelconque, il semble que le comportement autour des conditions d'association ait changé. Lors de l'affectation de l'objet associé, les conditions ne semblent pas être utilisé dans l'encart; uniquement lors de la lecture de l'association.
Ma solution a été de remplacer la méthode setter pour l'association:
has_one :photo, -> { photo_type: 'primary_photo'},
as: 'attachable',
dependent: :destroy
def photo=(photo)
photo.photo_type = 'primary_photo'
super
end
Pouvez-vous ajouter un modèle SecondaryPhoto comme:
class SecondaryPhoto < Photo
end
puis ignorez le: nom_classe du has_one: photo_secondaire?