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!