2010-06-04 6 views
2

J'ai des objets qui ont des commentaires. Dans le cadre d'un récapitulatif périodique de l'e-mail, je souhaite déterminer les commentaires pour une période et présenter les objets dans l'ordre de l'objet commenté le plus ancien en premier.regrouper les commentaires par objet parent, en ordonnant l'objet parent par le commentaire le plus ancien

données:

object_id comment_id 
30   40 
40   42 
32   41 
30   43 
32   44 

Sortie:

Objet # 30

  • commentaire 40
  • commentaire 43

Object # 40

  • commentaire 42

Objet # 32

  • commentaire 41
  • commentaire 44

J'utilise ce code pour obtenir les données d'un tableau intermédiaire - j'ai essayé de tout obtenir en un seul coup en utilisant .group_by (&: commentable_id) mais les données ne sont pas sorties dans le bon ordre.

comments = account.comments.all( 
    :conditions => ["comments.created_at > ?", 8.hours.ago], 
    :order => "comments.created_at asc").map { |c| [c.commentable_id,c.id] } 

=> [ [30,40], [40,42], [32,41], [30,43], [32,44] ] 

Si je peux obtenir ces données pour se transformer en la forme suivante, je pouvais itérer sur le tableau pour construire le contenu e-mail ...

[ [30,[40,43]], [40,[42]], [32,[41,44]] ] 

Mais je me demande si je fais C'est plus difficile que ce dont j'ai besoin ... Des conseils?

(j'utilise Rails 2.3 et Ruby ree-1.8.7)

Répondre

1

Vous pouvez utiliser un groupe avec un agrégat de tableau pour obtenir la forme de tableau que vous recherchez.

Les agrégats matriciels dépendent massivement de DB. MySQL est GROUP_CONCAT. Postgres 'est ARRAY_AGG. Sqlite n'en a pas une sortie, mais je sais que vous pouvez définir des fonctions d'agrégat personnalisées, ce n'est donc pas impossible.

que vous devez mentionner avez pas vraiment essayé d'exécuter ce code, mais voici quelque chose dans la bonne direction:

result = Object.all(
    :select => 'objects.id, GROUP_CONCAT(comment_id) AS comment_array', 
    :group => 'comments.id' 
).map { |c| [c.id, c.comment_array] } 

je la désignation du premier exemple, vous aurez donc besoin de changer « objet 'à tout ce que votre table est appelée. J'espère que cela a du sens. Rails n'a probablement pas de support intégré pour l'analyse d'un tableau, donc il retournera probablement une chaîne pour comment_array, et vous devrez peut-être l'analyser.

+0

merci pour la pointe ... voici le code que je suis allé avec ... résultat = Comment.all ( : select => 'commentable_id, GROUP_CONCAT (id) comme comment_array', : group => 'commentable_id' ) .map {| c | [c.commentable_id, c.comment_array]} .reverse – ryw

1

Avoir tous les commentaires pour un seul objet dans un seul bloc/élément fera sans aucun doute la vie plus facile en faisant une opération sur eux. Cependant, je n'irai pas jusqu'à les transformer en un tableau de tableaux car il s'agit déjà d'un tableau de tableaux. Je préférerais créer un hachage comme ceci:

comments_array = [ [30,40], [32,41], [40,42], [30,43], [32,44] ] 
obj_with_comments = {} 
comments_array.each do |x| 
    obj_with_comments[x.first] ||= [] 
    obj_with_comments[x.first] << x.last 
end 

obj_with_comments #=> {40=>[42], 30=>[40, 43], 32=>[41, 44]} 

Mais ce qui pose un autre problème qui est, hash ne sont pas commandés, si vous perdez votre commande d'une certaine façon aléatoire si vous venez d'itérer sur le hachage. Toutefois, vous pouvez créer un tableau d'objets puis parcourir le hachage comme suit:

objects = comments_array.collect{|x| x.first}.uniq 
objects #=> [30, 32, 40] 

# now get the hash value for each object key in order 
objects.each { |obj| puts obj_with_comments[obj].inspect } 

Espérons que cela a du sens.

+0

Oui, il est logique - merci - certainement une solution réalisable pour utiliser simplement 2 objets – ryw

0

Essayez ceci:

comments = account.comments.all(
    :conditions => ["comments.created_at > ?", 8.hours.ago], 
    :order => "comments.commentable_id ASC, comments.id ASC" 
).map { |c| [c.commentable_id,c.id] } 

Ceci renvoie le jeu de résultats suivant:

=> [ [30,40], [30,43], [32,41], [32,44], [40,42] ] 

Dans la requête ci-dessus, je me sers id pour le tri au lieu de create_at. Si vous utilisez MySQL et si les ID sont générés automatiquement, cette logique fonctionnera car le id d'un nouvel objet sera plus élevé que le id d'un objet plus ancien. Si vous n'autorisez pas l'édition de commentaires, cette logique fonctionnera.

Si vous voulez explicitement les trier par date, puis utilisez la syntaxe suivante:

comments = account.comments.all(
    :joins => "accounts AS accounts ON comments.commentable_type = 'Account' AND 
            comments.commentable_id = accounts.id", 
    :conditions => ["comments.created_at > ?", 8.hours.ago], 
    :order => "accounts.id ASC, comments.id ASC" 
).map { |c| [c.commentable_id,c.id] } 
+0

Je viens de modifier la question pour mettre la sortie des objets dans un ordre non croissant ... votre solution les trie simplement par ID, où je veux les classés par ordre alphabétique des plus anciens commentaires – ryw

+0

Mis à jour la réponse, jetez un oeil. –

Questions connexes