Non è possibile definire: unisce le condizioni in relazione has_many?
-
16-09-2019 - |
Domanda
Ho una tabella rapporto:
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
che collega gli animali per gli altri. Il modo migliore per prelevare le associazioni in 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)
E non riesco a trovare un modo per creare una vera e propria relazione has_many in rotaie con questo. A quanto pare, non c'è modo per fornire le condizioni per l'adesione has_many.
Attualmente sto 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}'
ma questo metodo ha il grande svantaggio di rompere la magia ActiveRecord. Per esempio:
@animal.friends.first
eseguirà il finder_sql senza limiti, recupero migliaia di righe, poi con il primo della matrice (e perdendo diversi preziosi secondi / REQ).
Credo che sia una caratteristica mancante da AR, ma mi piacerebbe essere sicuri prima :) Grazie
Soluzione
Si potrebbe risolvere questo a livello di database con una vista, che sarebbe il metodo corretto in ogni caso.
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
)
Se non si desidera avere doppie registrazioni per amici con rapporti in entrambi i modi si potrebbe fare questo:
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
)
Si avrebbe bisogno di un modo per decidere quale status_id da utilizzare nel caso in cui ci sono due quelli in conflitto. Ho scelto MIN (status_id), ma che probabilmente non è ciò che si desidera.
In Rails è possibile farlo ora:
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
Questo è fuori dalla mia testa e non testato. Inoltre, potrebbe essere necessario un po 'di plug-in per la gestione in vista rotaie (come "redhillonrails-core").
Altri suggerimenti
V'è un plugin che fa quello che si vuole.
C'è un post su di esso qui .
C'è un'alternativa qui .
Entrambi permettono di fare a condizioni di unione e sono solo utilizza l'inizializzazione pigra. Così si può utilizzare condizioni dinamiche. Trovo l'ex più bella, ma è possibile utilizzare questi ultimi se non si desidera installare plugin.
I due modi per creare molti a molti rapporti in record attivo sono has_and_belongs_to_many e has_many: attraverso. Questo sito critiques le differenze tra i due . Non è necessario scrivere alcun SQL utilizzando questi metodi.