2010-03-10 5 views
3

J'ai deux modèles:Est-ce possible en une seule requête?

class User 
end 

class Message 
belongs_to :sender, :class_name=> 'User' 
belongs_to :recipient, :class_name=> 'User' 
end 

Je veux aller chercher tous les copains d'un utilisateur donné commandés par la plus récente date de message de ce qui apparaît dans la conversation entre l'utilisateur donné et son copain et, si possible même requête, pour récupérer le numéro du message dans leur conversation.

Maintenant, je suis coincé à ceci:

Messages.all(:joins => :sender, 
    :conditions => ['sender_id = ? OR recipient_id = ?', some_user.id, some_user.id], 
    :select => 'users.*, sender_id, recipient_id, MAX(messages.created_at) as last_created, COUNT(messages.id) as messages_count', 
    :group => 'messages.sender_id, messages.recipient_id', 
    :order => 'last_created DESC' 

Cette requête produit cette sortie:

a)

users.* | sender_id | recipient_id | MAX(last_created) | messages_count 
user1 | 1   | 2   | bla    | bla 
user1 | 1   | 3   | bla    | bla 
user1 | 1   | 4   | bla    | bla 

Parce que les modèles joints par messages.sender_id = user.id je ne user1 enregistrements récupérés mais j'ai besoin user2, user3 et user4 enregistrements dans cette situation particulière A lorsque user1 a seulement envoyer des messages à ses copains.

b)

users.* | sender_id | recipient_id | MAX(last_created) | messages_count 
user2 | 2   | 1   | bla    | bla 
user3 | 3   | 1   | bla    | bla 
user4 | 4   | 1   | bla    | bla 

Dans la situation B, sinon, je dois ce que je veux avoir - les trois copains commandés par la date la plus récente de messages ce qui apparaît dans la conversation entre l'utilisateur donné et son copain.

c)

users.* | sender_id | recipient_id | MAX(last_created) | messages_count 
user1 | 1   | 2   | bla    | bla 
user3 | 3   | 1   | bla    | bla 
user4 | 4   | 1   | bla    | bla 

Situation C. user2 comme ami de user1 est manquant motif :joins => :sender. Sinon, si :joins => :recipient manque user3 et user4. C'est le craqueur. Peu importe comment nous rejoignons les modèles. Comment résoudre cette situation dans une requête?

+0

juste une chose: c'est ': class_name'. –

+0

Merci, faute d'orthographe – yukas

Répondre

2

Vous devez le joyau select_extra_columns retourner colonnes Adhérez/globales. En supposant que vous avez installé la gemme, modifiez votre modèle d'utilisateur comme indiqué ci-dessous.

class User 
    select_extra_columns 


    def friends_with_conversation 
    User.all(
    :select => "users.*, b.last_message_at, b.message_count", 
    :joins => " 
      RIGHT JOIN 
      (SELECT IF(a.sender_id=#{self.id}, a.recipient_id, 
          a.sender_id) AS friend_id, 
         MAX(a.created_at) AS last_message_at, 
         COUNT(a.id)  AS message_count 
       FROM  messages AS a 
       WHERE a.sender_id = #{self.id} OR 
         a.recipient_id = #{self.id} 
       GROUP BY IF(a.sender_id=#{self.id}, a.recipient_id, 
          a.sender_id) 
      ) AS b ON users.id = b.friend_id 
      ", 
     :order => "b.last_message_at DESC",  
     :extra_columns => {:last_message_at=>:datetime, :message_count => :integer} 
    ) 
    end 
end 

Maintenant, vous pouvez faire suite à des appels pour obtenir les détails d'amis.

user.friends_with_conversation.each do |friend| 
    p friend.name 
    p friend.last_message_at 
    p friend.message_count 
end 

Vous avez besoin la pierre précieuse pour revenir last_message_at et message_count dans l'objet User renvoyé par la requête.

Modifier Je ne suis pas familier avec PostgreSQL. La lecture cursive de la documentation suggère que SQL pourrait fonctionner.

:joins => " 
RIGHT JOIN 
(SELECT CASE WHEN a.sender_id=#{self.id} 
       THEN a.recipient_id 
       ELSE a.sender_id 
      END    AS friend_id, 
      MAX(a.created_at) AS last_message_at, 
      COUNT(a.id)  AS message_count 
    FROM  messages AS a 
    WHERE a.sender_id = #{self.id} OR 
      a.recipient_id = #{self.id} 
    GROUP BY CASE WHEN a.sender_id=#{self.id} 
       THEN a.recipient_id 
       ELSE a.sender_id 
      END 
) AS b ON users.id = b.friend_id 
" 
+0

Merci, je pense que cela peut effectivement fonctionner, mais je développe dans PostgreSQL et semble ne pas pouvoir comprendre cette syntaxe. Pourriez-vous ajouter une version de cette requête pour PostgreSQL? Merci encore. – yukas

+0

J'ai ajouté le PostgresSQL à ma réponse –

+0

Fonctionne comme un charme. Je vous remercie. Réponse très complexe et très utile. Et je découvre, tout fonctionne sans select_extra_columns gem. Pourquoi en ai-je besoin quand même? – yukas

1

On dirait que vous voulez une autre chaîne "join" (c'est-à-dire pas :joins => :sender). Est-ce que :joins => :recipient donnerait la bonne réponse pour la situation B?

Si ce n'est pas le cas, vous pouvez également transmettre un code SQL personnalisé à la clé :joins et joindre les tables comme vous le souhaitez.

On dirait qu'il ya une bonne couverture de tutoriel ici jointures: http://www.railway.at/articles/2008/04/24/database-agnostic-database-ignorant/

+0

: jointures =>: destinataire donne user1 pour tous les enregistrements dans B. Donc, il est incorrect. Tout le truc dans ça :). – yukas

+0

Juste édité. J'ajoute la situation C couvrant votre problème. – yukas

Questions connexes