Association polymorphe des Rails avec de multiples associations sur le même modèle
ma question est essentiellement la même que celle-ci: Polymorphe Association avec de multiples associations sur le même modèle
toutefois, la solution proposée/acceptée ne fonctionne pas, comme l'illustre un commentaire ultérieur.
j'ai un cours de Photo qui est utilisé sur toute mon application. Un poteau peut avoir une seule photo. Cependant, je veux réutiliser la relation polymorphique pour ajouter une photo secondaire.
Avant:
class Photo
belongs_to :attachable, :polymorphic => true
end
class Post
has_one :photo, :as => :attachable, :dependent => :destroy
end
désiré:
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
Toutefois, cela échoue car il ne peut pas trouver la classe "SecondaryPhoto". D'après ce que j'ai pu dire de cet autre fil, je voudrais faire:
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy
sauf pour le Post#secondary_photo qui renvoie simplement la même photo qui est jointe via l'association de photos, par exemple Post#photo === Post#secondary_photo. En regardant le SQL, il fait où type = "Photo" au lieu de, disons, "SecondaryPhoto" comme je voudrais...
? Merci!
10 réponses
j'ai fait cela dans mon projet.
le truc est que les photos ont besoin d'une colonne qui sera utilisée dans l'état has_one pour distinguer 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 en utilisant @post.build_photo
, le photo_type sera automatiquement pré-rempli avec le type correspondant, comme 'primary_photo'. ActiveRecord est intelligent assez pour le faire.
Rails 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
Vous devez fournir foreign_key selon ....able'ness ou Rails vont demander la colonne post_id dans la table photo. La colonne Attachable_type sera remplie de Rails magic comme SecondaryPhoto
l'Avenir de référence pour les gens de vérifier ce post
Cela 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 classe appelant lors du paramétrage le type polymorphe.
ci-dessus s'applique à has_many trop.
quelque chose comme suivre a fonctionné pour interroger, mais assigner de L'utilisateur à l'adresse n'a pas fonctionné
Classe D'Utilisateur
has_many :addresses, as: :address_holder
has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" },
class_name: "Address", foreign_key: "address_holder_id"
Adresse De Classe
belongs_to :address_holder, polymorphic: true
Je ne l'ai pas utilisé, mais j'ai cherché sur Google et j'ai regardé dans les sources de Rails et je pense que ce que vous cherchez est :foreign_type
. Essayez-le et dites si ça marche :)
has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'
je pense que le type dans votre question devrait être Post
au lieu de Photo
et, respectivement, il serait préférable d'utiliser SecondaryPost
comme il assigné au modèle Post
.
EDIT:
la réponse ci-dessus est totalement fausse. :foreign_type
est disponible dans le modèle polymorphique dans belongs_to
association pour spécifier le nom de la colonne qui contient le type de modèle associé.
comme je regarde dans les sources de Rails, cette ligne fixe ce type pour l'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 de 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 mettez à 0 si c'est la première photo, ou à 1 si c'est 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 cherchez, mais je ne trouve rien de mieux. Au fait, peut-être serait-il préférable d'utiliser l'association has_many
?
vous allez devoir rapiécer la notion de foreign_type dans une relation has_one. C'est ce que j'ai fait pour has_many. Dans une nouvelle .le fichier rb dans votre dossier initializers que j'ai appelé mine add_foreign_type_support.RB Il vous permet de spécifier ce que votre attachable_type est. Exemple: has_many photo :class_name => "Image": = > amovible, :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 #{}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
aucune des réponses précédentes ne m'a aidé à résoudre ce problème, donc je vais mettre cela ici au cas où quelqu'un d'autre se heurte à cela. Utilisation Des Rails 4.2+.
créer la migration (en supposant que vous avez déjà une table D'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
le programme d'Installation de votre polymorphe de l'association:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
configurer la classe où l'association sera appelée de:
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 appelez la méthode de compilation 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!
Espère que ça aide quelqu'un d'autre.
pour mongoid utiliser cette solution
a connu des moments difficiles après avoir découvert cette question, mais a obtenu une solution cool qui fonctionne
ajouter à votre Gemfile
gem 'mongoid-multiples, polymorphes'
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
pouvez-vous ajouter un secondaryphoto modèle comme:
class SecondaryPhoto < Photo
end
et ensuite sauter le nom de classe de has_one: secondary_photo?
aucune de ces solutions ne semble fonctionner sur les Rails 5. Pour une raison quelconque, il semble que le comportement autour de l'association a changé. Lors de l'attribution de l'objet lié, les conditions ne semblent pas être utilisées dans l'insertion; seulement lors de la lecture de l'association.
ma solution était de passer outre la méthode de 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