relación relaciones polimorfica ruby-on-rails polymorphic-associations

ruby-on-rails - polimorfica - relaciones laravel 5



Rieles: muchas a muchas relaciones polimórficas (7)

¿Has seguido ese enfoque de fuerza bruta?

class Task has_many :stores has_many :softwares has_many :offices has_many :vehicles def targets stores + softwares + offices + vehicles end ...

Puede que no sea tan elegante, pero para ser honesto, no es tan detallado, y no hay nada intrínsecamente ineficiente sobre el código.

Ver comentarios para actualizaciones.

He estado luchando por obtener una respuesta clara y directa sobre esta, ¡espero que esta vez la obtenga! : D Definitivamente tengo mucho que aprender aún con Rails, sin embargo, entiendo el problema que estoy enfrentando y realmente agradecería ayuda adicional.

  • Tengo un modelo llamado "Tarea".
  • Tengo un modelo abstracto llamado "Objetivo".
  • Me gustaría relacionar múltiples instancias de subclases de Destino a Tarea.
  • No estoy usando herencia de tabla única.
  • Me gustaría consultar la relación polimórfica para devolver un conjunto de resultados mixtos de subclases de Target.
  • Me gustaría consultar instancias individuales de subclases de Target para obtener tareas con las que están en una relación.

Entonces, me imagino que una relación polimórfica de muchos a muchos entre tareas y subclases de objetivos está en orden. Más detalladamente, podré hacer cosas como esta en la consola (y por supuesto en otro lugar):

task = Task.find(1) task.targets [...array of all the subclasses of Target here...]

¡Pero! Suponiendo que existan modelos "Tienda", "Software", "Oficina", "Vehículo", que son todas las subclases de "Objetivo", sería bueno también atravesar la relación en la otra dirección:

store = Store.find(1) store.tasks [...array of all the Tasks this Store is related to...] software = Software.find(18) software.tasks [...array of all the Tasks this Software is related to...]

Las tablas de la base de datos implícitas en las relaciones polimórficas parecen ser capaces de hacer este recorrido, pero veo algunos temas recurrentes al tratar de encontrar una respuesta que para mí venza el espíritu de las relaciones polimórficas:

  • Aún usando mi ejemplo, parece que las personas quieren definir Almacén, Software, Oficina, Vehículo en la tarea, lo que podemos decir de inmediato no es una relación polimórfica, ya que solo devuelve un tipo de modelo .
  • De forma similar al último punto, las personas aún desean definir Almacén, Software, Oficina y Vehículo en Tarea de una forma o forma. Lo importante aquí es que la relación es ciega a las subclases. Mis polimorfos inicialmente solo se interactuarán como objetivos, no como tipos de subclase individuales. La definición de cada subclase en Tarea nuevamente comienza a consumirse en el propósito de la relación polimórfica.
  • Veo que un modelo para la tabla de unión podría estar en orden, eso me parece algo correcto, excepto que agrega cierta complejidad que asumí que Rails estaría dispuesto a eliminar. Apelo a la inexperiencia en este caso.

Parece ser un pequeño agujero en la funcionalidad de los rieles o en el conocimiento colectivo de la comunidad. ¡Así que con suerte stackoverflow puede hacer una crónica de mi búsqueda de la respuesta!

Gracias a todos los que ayudan!


Aunque la respuesta propuesta por SFEley es excelente, hay algunas fallas:

  • La recuperación de tareas del objetivo (Tienda / Vehículo) funciona, pero no funciona al revés. Eso es básicamente porque no se puede atravesar a: mediante la asociación a un tipo de datos polimórficos porque el SQL no puede decir en qué tabla se encuentra.
  • Todos los modelos con: a través de asociación necesitan una asociación directa con la tabla intermedia
  • La asociación: a través de Asignación debe estar en plural
  • La sentencia: as no funcionará junto con: a través de, debe especificarlo primero con la asociación directa necesaria con la tabla intermedia

Con eso en mente, mi solución más simple sería:

class Assignment < ActiveRecord::Base belongs_to :task belongs_to :target, :polymorphic => true end class Task < ActiveRecord::Base has_many :assignments # acts as the the ''has_many targets'' needed def targets assignments.map {|x| x.target} end end class Store < ActiveRecord::Base has_many :assignments, as: :target has_many :tasks, :through => :assignment end class Vehicle < ActiveRecord::Base has_many :assignments, as: :target has_many :tasks, :through => :assignment, :as => :target end

Referencias: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through


Estoy de acuerdo con los demás que buscaría una solución que utilice una combinación de STI y la delegación sería mucho más fácil de implementar.

En el corazón de su problema es dónde almacenar un registro de todas las subclases de Target. ActiveRecord elige la base de datos a través del modelo STI.

Puede almacenarlos en una variable de clase en el Destino y usar la devolución de llamada heredada para agregar nuevos. A continuación, puede generar dinámicamente el código que necesitará de los contenidos de esa matriz y aprovechar method_missing.


La solución has_many_polymorphs que mencionas no es tan mala.

class Task < ActiveRecord::Base has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle] end

Parece hacer todo lo que quieras.

Proporciona los siguientes métodos:

a la tarea:

t = Task.first t.targets # Mixed collection of all targets associated with task t t.stores # Collection of stores associated with task t t.softwares # same but for software t.offices # same but for office t.vehicles # same but for vehicles

a Software, Tienda, Oficina, Vehículo:

s = Software.first # works for any of the subtargets. s.tasks # lists tasks associated with s

Si sigo los comentarios correctamente, el único problema restante es que no desea tener que modificar la aplicación / modelos / task.rb cada vez que crea un nuevo tipo de Subtarget. La forma de Rails parece requerir que modifique dos archivos para crear una asociación bidireccional. has_many_polymorphs solo requiere que cambies el archivo de tareas. Me parece una victoria. O al menos lo haría si no tuviera que editar el nuevo archivo de modelo de todos modos.

Hay algunas formas de evitar esto, pero parecen demasiado trabajo para evitar cambiar un archivo de vez en cuando. Pero si usted está en contra de la modificación de la tarea Tarea usted mismo para agregar a la relación polimórfica, aquí está mi sugerencia:

Mantenga una lista de subtargets, voy a sugerir en lib / subtargets una entrada por línea que es esencialmente table_name.underscore. (Las letras mayúsculas tienen un subrayado prefijado y luego todo está en minúscula)

store software office vehicle

Crea config / initializers / subtargets.rb y complétalo con esto:

SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym)

A continuación, querrá crear un generador personalizado o una nueva tarea de rake. Para generar su nuevo objetivo secundario y agregar el nombre del modelo al archivo de la lista de subobjetivos, definido anteriormente. Probablemente termines haciendo algo básico que hace el cambio y pasa los argumentos al generador estándar.

Lo siento, realmente no tengo ganas de guiarte en eso ahora, pero aquí hay some resources

Finalmente, reemplace la lista en la declaración has_many_polymorphs con SubtargetList

class Task < ActiveRecord::Base has_many_polymorphs :targets, :from => SubtargetList end

A partir de este punto, podría agregar un nuevo objetivo secundario con

$ script/generate subtarget_model home

Y esto actualizará automáticamente su lista polimórfica una vez que vuelva a cargar la consola o reinicie el servidor de producción.

Como dije, es mucho trabajo actualizar automáticamente la lista de subtarios. Sin embargo, si realiza esta ruta, puede ajustar el generador personalizado para asegurarse de que todas las partes requeridas del modelo de objetivo secundario estén allí cuando lo genere.


Puede combinar el polimorfismo y has_many :through para obtener un mapeo flexible:

class Assignment < ActiveRecord::Base belongs_to :task belongs_to :target, :polymorphic => true end class Task < ActiveRecord::Base has_many :targets, :through => :assignment end class Store < ActiveRecord::Base has_many :tasks, :through => :assignment, :as => :target end class Vehicle < ActiveRecord::Base has_many :tasks, :through => :assignment, :as => :target end

...Etcétera.


Puede que esta no sea una respuesta especialmente útil, pero simplemente, no creo que haya una manera fácil o automática de hacerlo. Al menos, no tan fácil como con asociaciones más simples para uno o para muchos.

Creo que crear un modelo de ActiveRecord para la tabla de unión es la forma correcta de abordar el problema. Una relación normal has_and_belongs_to_many asume una unión entre dos tablas especificadas, mientras que en su caso parece que desea unirse entre tasks y cualquiera de las stores , softwares , offices o vehicles (por cierto, ¿hay alguna razón para no usar STI? Aquí parece que ayudaría a reducir la complejidad al limitar el número de mesas que tiene). Entonces, en su caso, la tabla de unión también necesitaría saber el nombre de la subclase Target involucrada. Algo como

create_table :targets_tasks do |t| t.integer :target_id t.string :target_type t.integer :task_id end

Luego, en su clase Task , sus subclases Target y la clase TargetsTask , podría configurar has_ has_ ​​many asociaciones usando la palabra clave :through tal como se documenta en las páginas ActiveRecord :: Associations :: ClassMethods rdoc .

Pero aún así, eso solo lo lleva a una parte del camino, porque :through no sabrá usar el campo target_type como el nombre de la subclase Target . Para eso, es posible que pueda escribir algunos fragmentos SQL select / finder personalizados, también documentados en ActiveRecord :: Associations :: ClassMethods .

Con suerte, esto te mueve en la dirección correcta. Si encuentra una solución completa, ¡me encantaría verla!


Usando STI:

class Task < ActiveRecord::Base end class StoreTask < Task belongs_to :store, :foreign_key => "target_id" end class VehicleTask < Task belongs_to :vehicle, :foreign_key => "target_id" end class Store < ActiveRecord::Base has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id" end class Vehicle < ActiveRecord::Base has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id" end

En su base de datos necesitará: Task type:string y Task target_id:integer

La ventaja es que ahora tiene un modelo directo para cada tipo de tarea que puede ser específico.

Ver también STI y modelo polimórfico juntos

¡Aclamaciones!