2012-03-09 15 views
32

Tengo aquí un caso de asociación polimórfica e ITS.¿Por qué la asociación polimórfica no funciona para STI si la columna tipo de la asociación polimórfica no apunta al modelo base de STI?

# app/models/car.rb 
class Car < ActiveRecord::Base 
    belongs_to :borrowable, :polymorphic => true 
end 

# app/models/staff.rb 
class Staff < ActiveRecord::Base 
    has_one :car, :as => :borrowable, :dependent => :destroy 
end 

# app/models/guard.rb 
class Guard < Staff 
end 

Para que el assocation polimórfica para trabajar, de acuerdo con la documentación de la API de Assocation polimórfica, http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations que tengo que fijar borrowable_type a la base_class de modelos de infecciones de transmisión sexual, es decir, en mi caso es Staff.

La pregunta es: ¿Por qué no funciona si el borrowable_type se establece en la clase STI?

Algunas pruebas para demostrarlo:

# now the test speaks only truth 

# test/fixtures/cars.yml 
one: 
    name: Enzo 
    borrowable: staff (Staff) 

two: 
    name: Mustang 
    borrowable: guard (Guard) 

# test/fixtures/staffs.yml 
staff: 
    name: Jullia Gillard 

guard: 
    name: Joni Bravo 
    type: Guard 

# test/units/car_test.rb 

require 'test_helper' 

class CarTest < ActiveSupport::TestCase 
    setup do 
    @staff = staffs(:staff) 
    @guard = staffs(:guard) 
    end 

    test "should be destroyed if an associated staff is destroyed" do 
    assert_difference('Car.count', -1) do 
     @staff.destroy 
    end 
    end 

    test "should be destroyed if an associated guard is destroyed" do 
    assert_difference('Car.count', -1) do 
     @guard.destroy 
    end 
    end 

end 

pero parece ser cierto sólo con personal ejemplo. Los resultados son los siguientes:

# Running tests: 

F. 

Finished tests in 0.146657s, 13.6373 tests/s, 13.6373 assertions/s. 

    1) Failure: 
test_should_be_destroyed_if_an_associated_guard_is_destroyed(CarTest) [/private/tmp/guineapig/test/unit/car_test.rb:16]: 
"Car.count" didn't change by -1. 
<1> expected but was 
<2>. 

Gracias

Respuesta

26

Buena pregunta. Tenía exactamente el mismo problema con Rails 3.1. Parece que no puedes hacer esto, porque no funciona. Probablemente es un comportamiento intencionado. Aparentemente, usar asociaciones polimórficas en combinación con herencia de tabla única (ITS) en Rails es un poco complicado.

La documentación Rails actual de Rails 3.2 da este consejo para combinar polymorphic associations and STI:

Uso de asociaciones polimórficas en combinación con una sola mesa herencia (STI) es un poco difícil. Para que las asociaciones a funcionen como se esperaba, asegúrese de guardar el modelo base para los modelos STI en la columna de tipo de la asociación polimórfica.

En su caso, el modelo base sería "Staff", es decir, "borrowable_type" debería ser "Staff" para todos los elementos, no "Guard". Es posible hacer que la clase derivada aparezca como la clase base al usar "se convierte": guard.becomes(Staff). Se podría establecer la columna "borrowable_type" directamente a la clase base "personal", o como la documentación rieles sugiere, convertirlo automáticamente mediante

class Car < ActiveRecord::Base 
    .. 
    def borrowable_type=(sType) 
    super(sType.to_s.classify.constantize.base_class.to_s) 
    end 
+6

Eso significa que no puede asociar un Auto con una Guardia y recuperarlo con '@ guard.car', porque la Mesa del Coche siempre tendrá su columna 'borrowable_type' en 'Staff', y nunca en 'Guard'. Y eso significa que las asociaciones polimórficas en los modelos de STI son completamente inútiles. – dekeguard

+0

Es posible, esta respuesta funcionó para mí. Mmmm ... es extraño que no salga de la caja. No puedo ver por qué. – Macario

+0

+1 para el enlace y la descripción ... –

13

Una pregunta más viejo, pero el tema en los carriles 4 sigue siendo. Otra opción es crear/sobreescribir dinámicamente el método _type con una preocupación. Esto sería útil si su aplicación utiliza múltiples asociaciones polimórficas con STI y desea mantener la lógica en un solo lugar.

Esta preocupación atrapará todas las asociaciones polimórficas y asegurará que el registro siempre se guarde utilizando la clase base.

# models/concerns/single_table_polymorphic.rb 
module SingleTablePolymorphic 
    extend ActiveSupport::Concern 

    included do 
    self.reflect_on_all_associations.select{|a| a.options[:polymorphic]}.map(&:name).each do |name| 
     define_method "#{name.to_s}_type=" do |class_name| 
     super(class_name.constantize.base_class.name) 
     end 
    end 
    end 
end 

A continuación, sólo se incluyen en su modelo:

class Car < ActiveRecord::Base 
    belongs_to :borrowable, :polymorphic => true 
    include SingleTablePolymorphic 
end 
+0

¡Gracias! Funciona muy bien! – coderuby

+0

Es importante agregar el directorio de preocupaciones a la ruta de carga: [Agregar un directorio a la ruta de carga en Rails] (http://stackoverflow.com/a/12826228/1835865). De lo contrario, obtendrá un error constante no inicializado. – coderuby

0

Estoy de acuerdo con los comentarios generales que esto debería ser más fácil. Dicho eso, esto es lo que funcionó para mí.

tengo un modelo de la firma como la clase base y clientes actuales y potenciales como las clases de infecciones de transmisión sexual, como tan:

class Firm 
end 

class Customer < Firm 
end 

class Prospect < Firm 
end 

también tengo una clase polimórfica, Oportunidad, que se ve así:

class Opportunity 
    belongs_to :opportunistic, polymorphic: true 
end 

quiero hacer referencia a las oportunidades ya sea como

customer.opportunities 

o

prospect.opportunities 

Para hacer eso cambié los modelos de la siguiente manera.

class Firm 
    has_many opportunities, as: :opportunistic 
end 

class Opportunity 
    belongs_to :customer, class_name: 'Firm', foreign_key: :opportunistic_id 
    belongs_to :prospect, class_name: 'Firm', foreign_key: :opportunistic_id 
end 

me oportunidades de salvamento con una opportunistic_type de (la clase base) 'firme' y el respectivo cliente o prospecto ID que el opportunistic_id.

Ahora puedo obtener customer.opportunities y prospect.opportunities exactamente como yo quiera.

6

Acabo de tener este problema en Rails 4.2. He encontrado dos formas de resolver:

-

El problema es que Rails utiliza el nombre base_class de la relación de ITS.

La razón de esto se ha documentado en las otras respuestas, pero la esencia es que el equipo central parecen sentir que usted debería ser capaz de hacer referencia a la tabla en lugar de la clasepara una asociación ITS polimórfica.

No estoy de acuerdo con esta idea, pero no soy parte del equipo de Rails Core, por lo que no tengo mucha información para resolverlo.

Hay dos maneras de solucionarlo:

-

1) Introducir en el modelo de nivel:

class Association < ActiveRecord::Base 

    belongs_to :associatiable, polymorphic: true 
    belongs_to :associated, polymorphic: true 

    before_validation :set_type 

    def set_type 
    self.associated_type = associated.class.name 
    end 
end 

esto va a cambiar el registro {x}_type antes de la creación de los datos en el db. Esto funciona muy bien, y aún conserva la naturaleza polimórfica de la asociación.

2) Override Core ActiveRecord métodos

#app/config/initializers/sti_base.rb 
require "active_record" 
require "active_record_extension" 
ActiveRecord::Base.store_base_sti_class = false 

#lib/active_record_extension.rb 
module ActiveRecordExtension #-> http://stackoverflow.com/questions/2328984/rails-extending-activerecordbase 

    extend ActiveSupport::Concern 

    included do 
    class_attribute :store_base_sti_class 
    self.store_base_sti_class = true 
    end 
end 

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecordExtension) 

#### 

module AddPolymorphic 
    extend ActiveSupport::Concern 

    included do #-> http://stackoverflow.com/questions/28214874/overriding-methods-in-an-activesupportconcern-module-which-are-defined-by-a-cl 
    define_method :replace_keys do |record=nil| 
     super(record) 
     owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name 
    end 
    end 
end 

ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, AddPolymorphic) 

una manera más sistémica para solucionar el problema es editar el ActiveRecord métodos fundamentales que rigen. Utilicé referencias en this gem para averiguar qué elementos necesitaban ser reparados/reemplazados.

Esto no se ha probado y todavía necesita extensiones para algunas de las otras partes de los métodos básicos de ActiveRecord, pero parece que funciona para mi sistema local.

+0

referencia al código relacionado en el núcleo de rieles: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb#L16 – Lax

5

Hay una joya. https://github.com/appfolio/store_base_sti_class

Probado y funciona en varias versiones de AR.

+0

Great find, thx for sharing, esto funcionó muy bien en mi proyecto – SupaIrish

+0

Esta debería ser la mejor respuesta. Instalé la gema según las instrucciones y listo, todo funciona como se esperaba. ¡Gracias! –

Cuestiones relacionadas