ActiveRecord, has_many: through e Polymorphic Associations

117

Pessoal,

Quero ter certeza de que entendi isso corretamente. E, por favor, desconsidere o caso de herança aqui (SentientBeing), tentando se concentrar em modelos polimórficos em has_many: por meio de relacionamentos. Dito isso, considere o seguinte ...

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

Em um mundo perfeito, gostaria de, com um widget e uma pessoa, fazer algo como:

widget.people << my_person

No entanto, quando faço isso, percebi que o 'tipo' de 'garoupa' é sempre nulo em widget_groupings. No entanto, se eu quiser algo como o seguinte:

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

Então tudo funciona como eu normalmente esperava. Acho que nunca vi isso ocorrer com associações não polimórficas e só queria saber se isso era algo específico para este caso de uso ou se estou potencialmente diante de um bug.

Obrigado por qualquer ajuda!

Cory
fonte

Respostas:

162

Existe um problema conhecido com o Rails 3.1.1 que quebra essa funcionalidade. Se você está tendo este problema, primeiro tente atualizar, ele foi corrigido no 3.1.2

Você está tão perto. O problema é que você está usando incorretamente a opção: source. : a origem deve apontar para o relacionamento polimórfico belongs_to. Então tudo que você precisa fazer é especificar: source_type para o relacionamento que você está tentando definir.

Essa correção para o modelo do widget deve permitir que você faça exatamente o que está procurando.

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
fonte
Oh meu Deus, isso é tão dolorosamente óbvio que não posso acreditar que olhei para trás. Obrigado EmFi!
Cory
Sem problemas, acho que agonizei por cerca de um dia pensando em como fazer isso na primeira vez que o encontrei. Não ajudou o fato de que foi uma das primeiras coisas que tentei fazer no Rails que não envolvia seguir um tutorial / livro.
EmFi
1
Como scotkf aponta, há uma regressão no ActiveRecord 3.1.1 que bloqueia esse comportamento. A atualização para 3.1.2 permitirá que esta solução funcione.
EmFi
6
Mesma coisa que @Shtirlic mencionou. Existe uma maneira de não especificar source_type, então você tem um conjunto de resultados mistos? Se alguém resolveu isso, adoraria saber como.
Damon Aw,
3
Ainda funciona a partir do Rails 4.2.0. No entanto, existe alguma maneira de fazer isso atualmente sem source_type e duas associações separadas?
Emeka
3

Como mencionado acima, isso não funciona com o Rails 3.1.1 devido a um bug no: source, mas foi corrigido no Rails 3.1.2

Scottkf
fonte
-4

tem muitos: through e polymorphic não funcionam juntos. Se você tentar acessá-los diretamente, ocorrerá um erro. Se não me engano, você deve escrever widget.people e a rotina push.

Não acho que seja um bug, é apenas algo que ainda não foi implementado. Eu imagino que vejamos no recurso, porque todo mundo tem um caso em que poderia usá-lo.

cgr
fonte
6
Eles trabalham juntos. Por exemplo: has_many: subscriptions,: as =>: subscribable has_many: subscribers,: through =>: subscriptions
,:
Vou apresentar um exemplo do meu código com falha em um post separado em um futuro próximo :) Me pouparia muita dor de cabeça descobrir como contornar esse erro.
cgr