62 votes

Association polymorphe de rails avec plusieurs associations sur le même modèle

Ma question est essentiellement le même que celui ci: http://stackoverflow.com/questions/1168047/polymorphic-association-with-multiple-associations-on-the-same-model

Cependant, la proposition/accepté solution ne fonctionne pas, comme illustré par un intervenant plus tard.

J'ai une Photo de classe qui est utilisée partout dans mon application. Un poste peut avoir une seule photo. Cependant, je tiens à re-utiliser le polymorphe de la relation à ajouter une deuxième photo.

Avant:

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

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

Souhaitée:

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". Basé sur ce que je pourrais dire de cet autre thread, je veux faire:

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

À l'exception de l'appel de Post#secondary_photo retourne simplement la même photo qui est jointe via la Photo de l'association, par exemple Post#photo === Post#secondary_photo. En regardant le SQL, il fait OÙ type = "Photo" au lieu de, disons, "SecondaryPhoto" comme je voudrais...

Pensées? Merci!

76voto

hakunin Points 13171

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 distinguer les photos principales des photos 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 réside dans le fait que lorsque vous créez des photos en utilisant @post.build_photo , le type de photo sera automatiquement pré-rempli avec le type correspondant, comme "primaire_photo". ActiveRecord est assez intelligent pour le faire.

3voto

klew Points 9437

Je ne l'utilise pas, mais j'ai googlé autour de et le regarda dans les Rails de sources 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 a assigné à l' Post modèle.

EDIT:

La réponse ci-dessus est complètement faux. :foreign_type est disponible dans polymorphes modèle en belongs_to association de spécifier le nom de la colonne qui contient le type de modèle associé.

Comme je l'ai regarder dans les Rails de 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 de type. Comme je sais que vous ne pouvez rien faire avec elle.

Donc ma sugestion est d'ajouter une colonne à la Photo de modèle, exemple: photo_type. Et de le mettre à 0, si c'est la première photo, ou la valeur 1 si elle est sur la deuxième photo. Dans vos associations ajouter :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 peux pas trouver quelque chose de mieux. Par la manière, il serait peut-être préférable de simplement utiliser has_many association?

2voto

simonslaw Points 21

Vous devrez singer la notion de foreign_type dans la relation has_one. C'est ce que j'ai fait pour has_many. Dans un nouveau fichier .rb de votre dossier Initializer, j’ai appelé le mien add_foreign_type_support.rb. Il vous permet de spécifier le type de votre attachable_type. Exemple: has_many photo,: class_name => "Picture",: as => connectable,: 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
 

0voto

Rob Biedenharn Points 41

Pouvez-vous ajouter un modèle SecondaryPhoto comme:

 class SecondaryPhoto < Photo
end
 

puis ignorez le: nom_classe de has_one: secondaire_photo?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X