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

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top