5

Ich portiere eine Rails App auf Rails 4.2. Diese Rails-App enthält teilweise recht komplexen manuellen SQL-Code in Assoziationen - teils aufgrund von DB-Optimierungen (zB Subselects statt JOINs), teils aufgrund einer zum Zeitpunkt des Schreibens noch nicht praktikablen Alternative (Rails 3.0), teilweise sicher aufgrund von Unkenntnis (Ich hoffe, zumindest - das wäre leicht zu lösen).Portierung kompliziert has_many Beziehungen zu Rails> 4.1 (ohne finder_sql)

Beispiel: Eine InternalMessage-Klasse. Nachrichten können zwischen Benutzern gesendet werden (Empfänger einer InternalMessage und "Löschungen" von Nachrichten werden in InternalMessagesRecipients gespeichert, da es mehrere geben kann) und sie können gelesen, beantwortet, weitergeleitet und gelöscht werden. Der Verein sieht wie folgt aus:

class User < AR::Base 
    has_many :internal_messages, 
     :finder_sql => "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " + 
      ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
      ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}', 
     :counter_sql => 'SELECT count(DISTINCT(internal_messages.id)) FROM internal_messages ' + 
      ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
      ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' 
    # ... 
end 

Der wichtigste Teil der „OR“ Klausel am Ende ist - mit dieser Vereinigung Ich möchte beide bekommen empfangenen und gesendeten Nachrichten, die mit der Benutzertabelle separat verbunden sind:

da eine InternalMessage mehrere Empfänger haben kann (und auch an den Absender selbst gesendet werden kann).

F: Wie portiere ich diese finder_sql an eine Rails 4.2 kompatible has_many Definition?

Antwort

4

aktualisieren

lernte ich vor einer Weile, dass dies keinen Sinn macht. Eine has_many-Beziehung muss injektive Verbindungen mindestens in einer Richtung haben, so dass ein "OR" in einer SQL-Klausel keinen Sinn ergibt. Wie sollte eine CREATE Operation entscheiden, welche Bedingung erfüllt werden muss, um einen neuen Datensatz zu erstellen? Diese Beziehung wird nur per Definition gelesen, und daher handelt es sich nicht um eine has_many Beziehung.

In diesem Fall wäre eine einfache Klassenmethode (oder Bereich) die richtige Antwort anstelle von has_many. Verketten Ergebnisse mehrerer Abfragen etwas wie

verwenden
def internal_messages 
    InternalMessage.where(id: sent_message_ids + received_message_ids) 
end 

das resultierende Objekt verkettbar zu halten (das heißt @user.internal_messages.by_date etc.)

+0

Natürlich ist dies schlechter, wenn Sie Memoization von Ergebnissen wollen, besonders, damit sie mit 'reload' arbeiten. Gibt es eine Möglichkeit, Schienen zu erhalten, um diese Beziehungen ohne den Fremdschlüssel zu pflegen? – duane

3

Übergeben Sie den Proc, der die SQL-Zeichenfolge als Bereich enthält.

has_many :internal_messages, -> { proc { "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " + 
     ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
     ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' } } 
+0

OK, danke. Wie wäre es mit counter_sql? Wird nicht mehr benötigt? Mit Rails 3 habe ich SQL-Fehler erhalten, wenn ich counter_sql wegen DISTINCT nicht verwende. – Jens

+1

Ich habe das nie wirklich selbst gemacht, aber [diese Stackoverflow-Antwort] (http://stackoverflow.com/questions/22988321/replacement-for-has-many-counter-sql-in-rails-4-1) sagt dass Sie Ihre eigene Zählmethode in einem Block für has_many definieren können. Also ich denke, Sie können etwas wie 'def count proxy_association.owner.class.count_by_sql (" IHRE SQL HIER "); Ende im Block. – tyamagu2

+4

Update: Rails 4.2.5.1, Ruby 2.3.1p112 'has_many: Gespräche, -> {proc {" einige SQL "}}' NoMethodError: undefinierte Methode 'außer' für # Dimitri