2009-05-31 7 views
1

Ich schreibe ein Facebook-Nachrichtensystem für eine Rails App und ich habe Probleme beim Auswählen der Nachrichten für den Posteingang (mit will_paginate).Tricky MySQL Query für Messaging-System in Rails - Bitte Hilfe

Die Nachrichten sind in Threads organisiert, im Posteingang wird die letzte Nachricht eines Threads mit einem Link zum Thread angezeigt. Der Thread ist über eine parent_id 1-n-Beziehung mit sich selbst organisiert.

Bisher bin ich mit so etwas wie dies:

class Message < ActiveRecord::Base 
    belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id" 
    belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id" 
    has_many :children, :class_name => "Message", :foreign_key => "parent_id" 
    belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id" 
end 

class MessagesController < ApplicationController 

    def inbox 
    @messages = current_user.received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC" 
    end 
end 

Das bin ich alle Nachrichten gibt, aber für einen Thread der Thread selbst und die jüngste Meldung erscheint (und nicht nur die neueste Meldung). Ich kann auch nicht die GROUP BY-Klausel verwenden, weil für den Thread selbst (sozusagen das Elternteil) die parent_id = nil natürlich ist.

Wer hat eine Idee, wie man das elegant löst? Ich habe bereits darüber nachgedacht, die parent_id dem übergeordneten Element selbst hinzuzufügen und dann mit parent_id zu gruppieren, bin mir aber nicht sicher, ob das funktioniert.

Dank

+0

Ich würde nicht die Parent_id zum Eltern hinzufügen ..., die dann die Möglichkeit der Erweiterung der Benutzeroberfläche zu mehrstufigen Threads entfernt (Ihr Datenmodell unterstützt derzeit dies, aber ich rate Ihre UI wird nicht oder Sie würden diese Lösung nicht in Betracht ziehen :)) – workmad3

+0

Ich weiß, aber ich denke, dass ich diese Möglichkeit für eine schnelle und effiziente DB-Abfrage aufgeben kann. Grundsätzlich sind alle Nachrichten immer in einem Thread und sind immer nach Zeit geordnet (created_at stamp). Jede andere Art der Darstellung wäre sinnlos und könnte mit den Zeitstempeln modelliert werden. –

Antwort

0

Meine Lösung wäre, eine Liste von Threads zu bekommen (was ich von Nachrichten ohne Eltern-ID erhalten werden gehe davon könnte). Fügen Sie dann im Nachrichtenmodell eine Methode hinzu, mit der die letzte Nachricht im Thread gefunden und zurückgegeben wird. Sie können diese Methode dann verwenden, um die neueste Methode in jedem Thread zu erhalten und einen Link zum Kopf des Threads einfach einzufügen.

(Pseudo-) Code:

class Message < ActiveRecord::Base 
    belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id" 
    belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id" 
    has_many :children, :class_name => "Message", :foreign_key => "parent_id" 
    belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id" 

    def get_last_message_in_thread() 
    last_message = self 
    children.each do |c| 
     message = c.get_last_message_in_thread() 
     last_message = message if message.created_at > last_message.created_at 
    end 
    return last_message 
    end 
end 

class MessagesController < ApplicationController 

    def inbox 
    @messages = current_user.received_messages.find_by_parent_id(Null).paginate :page => params[:page], :per_page => 10, :order => "created_at DESC" 
    end 
end 

Sie wahrscheinlich viel besser, als wenn eine rekursive Funktion tun könnten die letzte Nachricht im Thread zu finden, aber es ist die einfachste Lösung Ich denke, kann das zu demonstrieren, Idee. Ich bin mir auch nicht sicher, ob ich die korrekte Syntax für das Auffinden von unbestimmten Eltern-IDs in der Inbox-Funktion habe, weshalb ich den Code als Pseudo-Code markiert habe :)

+0

Auch etwas, das ich bei meiner Lösung bemerkt habe ... Sie müssten etwas anderes bestellen, um das korrekte created_date zu erhalten. Wahrscheinlich etwas in der Art von: @messages = current_user.received_messages.find_by_parent_id (Null) .sort_by {| m | m.get_last_message_in_thread(). created_at} .paginate: page => params [: page],: per_page => 10 obwohl das für einen Einzeiler ziemlich hässlich ist. – workmad3

+0

Danke für Ihre Lösung. Das Problem hier ist, dass ich die letzten child-Nachrichten (10 pro Seite) im Posteingang anzeigen möchte. Wenn ich die Threads zuerst erhalte, muss ich ALLE Threads holen und dann nach den 10 letzten Childs in ALLEN Threads suchen (weil es möglich ist, dass der älteste Thread sozusagen das jüngste Kind enthält). Das ist nicht effizient. Ich suche nach einer Lösung, die die DB-Abfragen minimiert. –

0

Der einzige effiziente Weg wäre, ein Thread-Modell zu verwenden GROUP BY, wie Sie erwähnt haben - Alles andere würde eine Iteration über die Nachrichten erfordern.

Lese Update in den Kommentaren

+0

Das habe ich mir gedacht! Aber wenn ich die parent_id zum Eltern selbst hinzufüge, bekomme ich ein wirklich seltsames Verhalten: Wenn ich "GROUP BY parent_id ORDER BY created_at DESC" verwende, erhalte ich nicht den neuesten, sondern den ältesten. Ich muss erneut in den MySQL Doc schauen. Ich werde hier meine Lösung posten. –

+0

Ahh - Ich verstehe was du meinst. GROUP BY verwendet die Standardsortierreihenfolge Ihrer Tabelle. Sie müssen eine innere Abfrage verwenden, um MAX (created_at) für jeden Thread zu finden und einen Join für diesen durchzuführen (ich glaube nicht, dass die Rails-Helfer Ihnen in diesem Fall helfen werden). – Matt

+0

Ja, aber die Verwendung einer Unterabfrage wird meiner Leistung nicht helfen, also ist es in gewisser Weise eine schlechte Lösung. Ich weiß von meinen letzten Projekten, dass Leute viel PM in den Sozialen Netzen senden, die Mitteilungs-Tabelle neigt dazu, eine der größten zu sein, und die Fragen, um diese zu bekommen, sollten schnell sein und viele Daten gut behandeln. –

0

geben die Eltern selbst als Elternteil macht es sehr einfach Abfragen zu erstellen, die auf der ganzen Thread arbeiten, weil Sie können Gruppe (oder etwas ähnliches) von parent_id.

, wenn Sie die Eltern anders behandeln, alle Ihre Fragen haben diese auch kümmern

0

ich die einzige gute Lösung gedacht, ein zweites Modell mit den neuesten Meldungen für jeden Thread gespeichert werden (wegen der Performance-Probleme Bei Verwendung von GROUP BY mit einem Subselect, siehe meine Kommentare). Es wird nicht viel Platz in der Datenbank benötigen, da wir nur IDs und keinen Text oder sogar Blobs speichern.

Das RecentMessages Modell würde wie folgt aussehen:

create_table :recent_messages do |t| 
    t.integer :sender_id 
    t.integer :recipient_id 
    t.integer :message_id 
    t.integer :message_thread_id 

    t.timestamps 
end 

class RecentMessage < ActiveRecord::Base 

    belongs_to :message 
    belongs_to :message_thread, :class_name => 'Message' 
    belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id" 
    belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id" 

end 

Die Hauptidee ist: Alle Nachrichten in einem Modell gespeichert werden (Nachrichten). Immer wenn eine neue Nachricht zu einem Thread hinzugefügt wird (oder ein Thread erstellt wird), passieren zwei Dinge (z.B.mit einem after_save Rückruf):

  • Speichern der neuen Nachricht in der RecentMessages Modell (das bedeutet SENDER_ID, recipient_id, message_id, message_thread_id (= parent_id || id))
  • die neueste Nachricht abrufen (aus diesem Thread in Nachrichten), wobei sender_id == recipient_id und umgekehrt (Hinweis: Dies funktioniert nur, wenn das Nachrichtenmodell nur Nachrichten zwischen 2 Benutzern unterstützen soll) und speichert es auch im Modell "RecentMessages" (falls gefunden und wenn es nicht bereits vorhanden ist)

Natürlich sollte es nur max. 2 recent_messages, die zu jedem Zeitpunkt in der DB für jeden message_thread gespeichert sind.

Wenn man das heißt dem Posteingang zeigen will, muss Folgendes geschehen:

@messages = current_user.recent_received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC", :include => :message 

Das ist das Beste, was ich bisher herausgefunden. Ich denke immer noch, dass es hässlich ist, aber es ist schnell und es funktioniert. Wenn jemand eine bessere Lösung findet, werde ich dankbar sein!

0

Ich weiß nicht, wie dies in Rails zu erreichen, aber das ist, wie ich es in MySQL direkt tat

select * from Nachrichten, bei denen message_id in ( select max (message_id) von Nachrichten, bei denen to_uid = 51 group by thread_id ) order by timestamp desc

Ich habe eine Unterabfrage verwendet, um die letzte Nachricht in einem Thread zu erfassen und dann die Hauptabfrage, um alle Felder für die Nachrichten in der Unterabfrage zu erfassen.