2009-04-25 9 views
0

Ich habe eine Beziehung Tabelle:Kann nicht definieren: verbindet Bedingungen in has_many Beziehung?

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 

Tiere zu anderen verbinden. Der beste Weg, Verbände in SQL retreive ist:

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) 

Und ich kann nicht einen Weg finden, eine richtige Beziehung has_many in Schienen mit dieser zu schaffen. Anscheinend gibt es keine Möglichkeit, Beitrittsbedingungen für has_many bereitzustellen.

Ich verwende derzeit eine 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}' 

aber diese Methode hat den großen Nachteil, Active Magie zu brechen. Zum Beispiel:

@animal.friends.first 

die finder_sql ohne Begrenzung, führen Tausende von Zeilen abgerufen, dann die erste Aufnahme des Arrays (und einige Sekunden lang wertvollen /req zu verlieren).

Ich denke, es ist ein fehlendes Feature von AR, aber ich möchte, dass der erste sein :) Dank

Antwort

2

Sie könnten dies auf Datenbankebene mit einer Ansicht lösen, die sowieso die richtige Methode wäre.

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 
) 

Wenn Sie nicht in beide Richtungen mit Beziehungen doppelte Einträge für Freunde haben, sollten Sie dies tun könnte:

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 
) 

Sie einen Weg brauchen würde, um zu entscheiden, welche status_id zu verwenden, falls es zwei widersprüchliche Einsen. Ich habe MIN (status_id) gewählt, aber das ist wahrscheinlich nicht das, was Sie wollen.

In Rails können Sie dies jetzt tun:

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 

Diese aus meinem Kopf und nicht getestet. Außerdem benötigen Sie möglicherweise ein Plugin für die Ansichtsverarbeitung in Rails (wie "redhillonrails-core").

+0

Danke, ich mag die Idee! Ich werde so schnell wie möglich testen und Sie wissen lassen. – Gravis

+0

Entschuldigung für die lange Verzögerung, bevor ich darauf antwortete, hatte ich endlich Zeit, Ihre Lösung zu testen, und es funktioniert wie ein Zauber, out of the box. – Gravis

0

Die zwei Wege viele zu viele Beziehungen in aktiven Datensatz sind has_and_belongs_to_many und has_many zu schaffen: durch. This site kritisiert die Unterschiede zwischen den beiden. Sie müssen mit diesen Methoden kein SQL schreiben.

+0

Hallo, vielen Dank für den Link! Wie auch immer, hattm erlaubt keine Join-Bedingungen :( Daher muss ich die Verbindung mit: conditions machen, und da ich mehr als 100.000 Zeilen (~ 400.000) habe, möchte ich ein wirklich optimiertes SQL haben Abfrage. Mit einem HABTM würde ich nur die Hälfte der Ergebnisse erhalten, da ich nach relations in foreign_key AND association_foreign_key suche. danke – Gravis

1

Es gibt ein Plugin, das das macht, was Sie wollen.

Es gibt einen Beitrag darüber here.

Es gibt eine Alternative here.

Beide ermöglichen es Ihnen, Beitrittsbedingungen zu tun und verwenden nur faule Initialisierung. So können Sie dynamische Bedingungen verwenden. Ich finde das frühere hübscher, aber Sie können letzteres verwenden, wenn Sie keine Plugins installieren möchten.

Verwandte Themen