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!

50
demandé sur Community 2010-03-22 20:41:48

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.

68
répondu Max Chernyak 2013-08-15 06:02:29

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

19
répondu Artem Aminov 2015-11-09 17:53:10

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.

4
répondu JellyFishBoy 2014-10-13 14:06:31

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
4
répondu sangyongjung 2014-10-22 17:31:34

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 ?

2
répondu klew 2010-03-23 21:12:37

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
2
répondu simonslaw 2010-06-10 06:39:15

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.

2
répondu PR Whitehead 2017-04-04 11:26:22

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
1
répondu comonitos 2015-06-14 15:07:52

pouvez-vous ajouter un secondaryphoto modèle comme:

class SecondaryPhoto < Photo
end

et ensuite sauter le nom de classe de has_one: secondary_photo?

0
répondu Rob Biedenharn 2010-03-22 20:19:56

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
0
répondu Chris Edwards 2017-10-26 09:00:27