No se puede definir: se une a las condiciones en relación has_many?
-
16-09-2019 - |
Pregunta
Tengo una tabla de relación:
create_table "animal_friends", :force => true do |t|
t.integer "animal_id"
t.integer "animal_friend_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "status_id", :default => 1
end
vinculación de los animales a los demás. La mejor manera es posible recuperar asociaciones en SQL es:
SELECT animals.*
from animals join animal_friends as af
on animals.id =
case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end
WHERE #{id} in (af.animal_id, af.animal_friend_id)
Y no puedo encontrar una manera de crear una relación has_many adecuada en los carriles con esto. Al parecer, no hay manera de proporcionar las condiciones de unión para has_many.
Actualmente estoy usando un finder_sql:
has_many :friends, :class_name => "Animal", :finder_sql => 'SELECT animals.* from animals join animal_friends as af on animals.id = case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end ' +
'WHERE #{id} in (af.animal_id, af.animal_friend_id) and status_id = #{Status::CONFIRMED.id}'
pero este método tiene la gran desventaja de romper la magia activerecord. Por ejemplo:
@animal.friends.first
ejecutará la finder_sql sin límite, ir a buscar miles de filas, a continuación, teniendo el primero de la serie (y perder varios preciosos segundos / req).
Creo que es una característica que falta de la AR, pero me gustaría estar seguro primero :) Gracias
Solución
Usted podría resolver esto en el nivel de base de datos con vistas, lo cual sería el método correcto de todos modos.
CREATE VIEW with_inverse_animal_friends (
SELECT id,
animal_id,
animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
UNION
SELECT id,
animal_friend_id AS animal_id,
animal_id AS animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
)
Si no quieres tener entradas dobles para las relaciones con los amigos en ambos sentidos que podría hacer esto:
CREATE VIEW unique_animal_friends (
SELECT MIN(id), animal_id, animal_friend_id, MIN(created_at), MAX(updated_at), MIN(status_id)
FROM
(SELECT id,
animal_id,
animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
UNION
SELECT id,
animal_friend_id AS animal_id,
animal_id AS animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends) AS all_animal_friends
GROUP BY animal_id, animal_friend_id
)
Se necesitaría una forma de decidir qué status_id a utilizar en caso de que haya dos los conflictivos. Elegí MIN (status_id) pero que no es probable que lo que desea.
En Rails puede hacer esto ahora:
class Animal < ActiveRecord::Base
has_many :unique_animal_friends
has_many :friends, :through => :unique_animal_friends
end
class UniqueAnimalFriend < ActiveRecord::Base
belongs_to :animal
belongs_to :friend, :class_name => "Animal"
end
Esto está fuera de mi cabeza y no probado. Además, es posible que tenga algún plug-in para el manejo vista en rieles (como "redhillonrails-core").
Otros consejos
Hay un plugin que hace lo que quiere.
Hay un post sobre ello aquí .
Hay una alternativa aquí .
Ambos permiten que usted hace a las condiciones de unión y es justo con la inicialización perezosa. Así que usted puede utilizar condiciones dinámicas. Me parece que el ex más bonita, pero puede utilizar este último si no desea instalar plugins.
Los dos maneras de crear relaciones muchos a muchos en registro activo son has_and_belongs_to_many y has_many: a través. Este sitio critica las diferencias entre los dos . Usted no tiene que escribir ningún SQL utilizando estos métodos.