ActiveRecord, has_many: through y asociaciones polimórficas

117

Amigos

Quiero asegurarme de que entiendo esto correctamente. Y, por favor, ignore el caso de la herencia aquí (SentientBeing), tratando de centrarse en los modelos polimórficos en has_many: a través de relaciones. Dicho esto, considere lo siguiente ...

class Widget < ActiveRecord::Base
  has_many :widget_groupings

  has_many :people, :through => :widget_groupings, :source => :person, :conditions => "widget_groupings.grouper_type = 'Person'"
  has_many :aliens, :through => :widget_groupings, :source => :alien, :conditions => "video_groupings.grouper_type = 'Alien'"
end

class Person < ActiveRecord::Base
  has_many :widget_groupings, :as => grouper
  has_many :widgets, :through => :widget_groupings
end

class Alien < ActiveRecord::Base
  has_many :widget_groupings, :as => grouper
  has_many :widgets, :through => :widget_groupings  
end

class WidgetGrouping < ActiveRecord::Base
  belongs_to :widget
  belongs_to :grouper, :polymorphic => true
end

En un mundo perfecto, me gustaría, dado un widget y una persona, hacer algo como:

widget.people << my_person

Sin embargo, cuando hago esto, he notado que el 'tipo' del 'agrupador' siempre es nulo en widget_groupings. Sin embargo, si hago algo como lo siguiente:

widget.widget_groupings << WidgetGrouping.new({:widget => self, :person => my_person}) 

Entonces todo funciona como normalmente hubiera esperado. No creo que haya visto que esto ocurra con asociaciones no polimórficas y solo quería saber si esto era algo específico para este caso de uso o si potencialmente estoy mirando un error.

¡Gracias por cualquier ayuda!

Cory
fuente

Respuestas:

162

Hay un problema conocido con Rails 3.1.1 que rompe esta funcionalidad. Si tiene este problema, primero intente actualizar, se ha solucionado en 3.1.2

Estás tan cerca. El problema es que estás haciendo un mal uso de la opción: source. : la fuente debe apuntar a la relación polimórfica pertenece_ a. Luego, todo lo que necesita hacer es especificar: source_type para la relación que está tratando de definir.

Esta corrección del modelo de widget debería permitirle hacer exactamente lo que está buscando.

class Widget < ActiveRecord::Base
  has_many :widget_groupings

  has_many :people, :through => :widget_groupings, :source => :grouper, :source_type => 'Person'
  has_many :aliens, :through => :widget_groupings, :source => :grouper, :source_type => 'Alien'
end
EmFi
fuente
Oh, Dios mío, eso es tan dolorosamente obvio que no puedo creer que lo miré fijamente. ¡Gracias EmFi!
Cory
No hay problema, creo que estuve agonizando durante un día sobre cómo hacer esto la primera vez que lo encontré. No ayudó que fuera una de las primeras cosas que intenté hacer en Rails que no implicaba seguir un tutorial / libro.
EmFi
1
Como señala scotkf, hay una regresión en ActiveRecord 3.1.1 que bloquea este comportamiento. La actualización a 3.1.2 permitirá que esta solución funcione.
EmFi
6
Lo mismo que mencionó @Shtirlic. ¿Hay alguna forma de no especificar source_type, por lo que tiene un conjunto de resultados mixtos? Si alguien resolvió esto, me encantaría saber cómo.
Damon Aw
3
Todavía funciona a partir de Rails 4.2.0. Sin embargo, ¿hay alguna forma de lograr esto en estos días sin source_type y dos asociaciones separadas?
Emeka
3

Como se mencionó anteriormente, esto no funciona con rails 3.1.1 debido a un error en: source, pero se corrigió en Rails 3.1.2

Scottkf
fuente
-4

tiene muchos: completo y polimórfico no funcionan juntos. Si intenta acceder a ellos directamente, debería arrojar un error. Si no me equivoco, tienes que escribir a mano widget.people y la rutina push.

No creo que sea un error, es algo que aún no se ha implementado. Me imagino que lo vemos en la función, porque todos tienen un caso en el que podrían usarlo.

cgr
fuente
6
Trabajan juntos. Por ejemplo: has_many: subscriptions,: as =>: subscribable has_many: subscribers
,:
Daré un ejemplo de mi código defectuoso como una publicación separada en un futuro cercano :) Me ahorraría mucho dolor de cabeza descubrir cómo evitar ese error.
cgr