Rails Associazione polimorfica con più associazioni sullo stesso modello

La mia domanda è essenzialmente la stessa di questa: associazione polimorfica con più associazioni sullo stesso modello

Tuttavia, la soluzione proposta / accettata non funziona, come illustrato da un commentatore più tardi.

Ho una class fotografica che viene utilizzata in tutta la mia app. Un post può avere una singola foto. Tuttavia, voglio riutilizzare la relazione polimorfica per aggiungere una foto secondaria.

Prima:

class Photo belongs_to :attachable, :polymorphic => true end class Post has_one :photo, :as => :attachable, :dependent => :destroy end 

desiderata:

 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 

Tuttavia, ciò non riesce poiché non riesce a trovare la class “SecondaryPhoto”. Sulla base di quello che potrei dire da quell’altro thread, vorrei fare:

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

Tranne che chiamare Post # secondary_photo restituisce semplicemente la stessa foto allegata tramite l’associazione Foto, ad esempio Post # foto === Post # foto secondaria. Guardando l’SQL, WHERE type = “Foto” invece di, ad esempio, “SecondaryPhoto” come vorrei …

Pensieri? Grazie!

L’ho fatto nel mio progetto.

Il trucco è che le foto hanno bisogno di una colonna che verrà utilizzata in condizione has_one per distinguere tra foto primarie e secondarie. Presta attenzione a cosa succede in :conditions qui.

 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 

Il bello di questo approccio è che quando crei foto usando @post.build_photo , il photo_type verrà automaticamente popolato con il tipo corrispondente, come “primary_photo”. ActiveRecord è abbastanza intelligente per farlo.

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 

È necessario fornire foreign_key secondo …. able’ness o Rails chiederà la colonna post_id nella tabella delle foto. La colonna Attachable_type si riempie con Rails magic come SecondaryPhoto

Riferimento futuro per le persone che controllano questo post

Questo può essere ottenuto utilizzando il seguente codice …

Rotaie 3:

 has_one :banner_image, conditions: { attachable_type: 'ThemeBannerAttachment' }, class_name: 'Attachment', foreign_key: 'attachable_id', dependent: :destroy 

Rotaie 4:

 has_one :banner_image, -> { where attachable_type: 'ThemeBannerAttachment'}, class_name: 'Attachment', dependent: :destroy 

Non sei sicuro del perché, ma in Rails 3, devi fornire un valore foreign_key insieme alle condizioni e class_name. Non utilizzare ‘as:: attachable’ poiché questo utilizzerà automaticamente il nome della class chiamante quando si imposta il tipo polimorfico.

Quanto sopra vale anche per has_many.

Qualcosa come il seguente ha funzionato per l’interrogazione, ma l’assegnazione da Utente a indirizzo non ha funzionato

Classe utente

 has_many :addresses, as: :address_holder has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" }, class_name: "Address", foreign_key: "address_holder_id" 

Classe di indirizzo

 belongs_to :address_holder, polymorphic: true 

Non l’ho usato, ma ho cercato su Google e ho cercato nelle fonti di Rails e penso che quello che stai cercando sia :foreign_type . Provalo e dillo se funziona 🙂

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

Penso che il tipo nella tua domanda dovrebbe essere Post invece di Photo e, rispettivamente, sarebbe meglio usare SecondaryPost come assegnato al modello Post .

MODIFICARE:

La risposta sopra è completamente sbagliata. :foreign_type è disponibile nel modello polimorfico in belongs_to per specificare il nome della colonna che contiene il tipo di modello associato.

Mentre guardo nei sorgenti di Rails, questa linea imposta questo tipo per associazione:

 dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] 

Come puoi vedere usa base_class.name per ottenere il nome del tipo. Per quanto ne so tu non puoi farci nulla.

Quindi la mia ragione è di aggiungere una colonna al modello Photo, ad esempio: photo_type . E impostalo su 0 se è la prima foto o impostalo su 1 se è la seconda foto. Nelle tue associazioni aggiungi :conditions => {:photo_type => 0} e :conditions => {:photo_type => 1} , rispettivamente. So che non è una soluzione che stai cercando, ma non riesco a trovare nulla di meglio. A proposito, forse sarebbe meglio usare solo has_many association?

Dovrai ricorrere alla patch delle scimmie sulla nozione di foreign_type in una relazione has_one. Questo è quello che ho fatto per has_many. In un nuovo file .rb nella tua cartella di inizializzatori ho chiamato il mio add_foreign_type_support.rb. Permette di specificare cosa deve essere il tuo tipo attachable_. Esempio: has_many foto,: class_name => “Immagine”,: as => attaccabile,: 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 

Nessuna delle risposte precedenti mi ha aiutato a risolvere questo problema, quindi inserirò questo qui nel caso in cui qualcun altro si imbattesse in questo. Utilizzando Rails 4.2 +.

Crea la migrazione (ammesso che tu abbia già una tabella Indirizzi):

 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 

Imposta la tua associazione polimorfica:

 class Address < ActiveRecord::Base belongs_to :addressable, polymorphic: true end 

Imposta la class da cui verrà chiamata l'associazione:

 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 

Il trucco è che devi chiamare il metodo build nell'istanza Order o la colonna scope non verrà popolata.

Quindi questo NON funziona:

 address = {attr1: "value"... etc...} order = Order.new(bill_address: address) order.save! 

Tuttavia, questo funziona.

 address = {attr1: "value"... etc...} order = Order.new order.build_bill_address(address) order.save! 

Spero che aiuti qualcun altro.

Per uso mongoidale questa soluzione

Ci sono stati momentjs difficili dopo aver scoperto questo problema, ma ho trovato una soluzione interessante che funziona

Aggiungi al tuo Gemfile

gem ‘mongoid-multiplo-polimorfico’

E questo funziona come un fascino:

  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 

Puoi aggiungere un modello SecondaryPhoto come:

 class SecondaryPhoto < Photo end 

e poi salta: class_name da has_one: secondary_photo?

Nessuna di queste soluzioni sembra funzionare su Rails 5. Per qualche ragione, sembra che il comportamento attorno alle condizioni dell’associazione sia cambiato. Quando si assegna l’object correlato, le condizioni non sembrano essere utilizzate nell’inserto; solo leggendo l’associazione.

La mia soluzione era di scavalcare il metodo setter per l’associazione:

 has_one :photo, -> { photo_type: 'primary_photo'}, as: 'attachable', dependent: :destroy def photo=(photo) photo.photo_type = 'primary_photo' super end