2010-07-16 7 views
4

Disons que j'ai deux modèles post et Catégorie:collection ActiveRecord

class Post < ActiveRecord::Base 
    belongs_to :category 
end 

class Category < ActiveRecord::Base 
    has_many :posts 
end 

Y at-il une méthode qui me permettra de faire quelque chose comme

posts = Post.find(:all) 

p = Array.new 

p[1] = posts.with_category_id(1) 
p[2] = posts.with_category_id(2) 
p[3] = posts.with_category_id(3) 
... 

or 

p = posts.split_by_category_ids(1,2,3) 

=> [posts_with_category_id_1, 
    posts_with_category_id_2, 
    posts_with_category_id_3] 

En d'autres termes, « séparés 'la collection de tous les messages dans les tableaux par des identifiants de catégorie sélectionnés

Répondre

11

Essayez la fonction group_by sur Array classe:

posts.group_by(&:category_id) 

Reportez-vous à l'API documentation pour plus détails.

caveat:

groupement ne doit pas effectuée dans le code Ruby lorsque l'ensemble de données potentiel peut être grand. J'utilise la fonction group_by lorsque la taille maximale du jeu de données est < 1000. Dans votre cas, vous pouvez avoir des milliers de Post s. Le traitement d'un tel tableau mettra à rude épreuve vos ressources. Fiez-vous à la base de données pour effectuer le regroupement/tri/agrégation etc.

Voici une façon de le faire (solution similaire est suggérée par nas)

# returns the categories with at least one post 
# the posts associated with the category are pre-fetched 
Category.all(:include => :posts, 
    :conditions => "posts.id IS NOT NULL").each do |cat| 
    cat.posts 
end 
+0

La solution 'group_by' est exactement ce que je cherchais, merci! – Vincent

+0

Grande réponse merci – ALFmachine

0

Bien sûr, mais compte tenu de vos relations de modèle, je pense que vous devez regarder dans l'autre sens.

p = [] 
1.upto(some_limit) do |n| 
    posts = Category.posts.find_by_id(n) 
    p.push posts if posts 
end 
+0

Oui, mais cette façon, il courrez some_limit requêtes SQL et je voudrais utiliser une seule requête (messages = Post.find (: tous)), si possible. – Vincent

0

Quelque chose comme cela pourrait fonctionner (par exemple méthode de post, non testé):

def split_by_categories(*ids) 
    self.inject([]) do |arr, p| 
    arr[p.category_id] ||= [] 
    arr[p.category_id] << p if ids.include?(p.category_id) 
    arr 
    end.compact 
end 
0

Au lieu d'obtenir tous les messages et faire ensuite une opération sur eux pour les classer par catégorie qui est une performance peu un exercice intense, je préférerais plutôt d'utiliser le chargement désireux comme si

categories = Category.all(:include => :posts) 

Cela va générer une requête SQL pour récupérer tous vos messages et objets de la catégorie. Ensuite, vous pouvez facilement itérer sur eux:

p = Array.new 
categories.each do |category| 
    p[1] = category.posts 
    # do anything with p[1] array of posts for the category 
end 
+0

@nas cela retournera même les catégories sans messages. Vous devez ajouter des conditions supplémentaires pour filtrer les catégories sans publications. –

+0

@KandadaBoggu Je ne pense pas que cela importe si vous obtenez des catégories sans messages ou non, car selon la question @Vincent fait des opérations sur les postes ne sont pas sur les catégories. Et quand vous appellerez 'category.posts', vous obtiendrez un tableau vide et l'itération à travers un tableau vide n'effectuera pas votre bloc de code donc aucun effet. Donc, à mon humble avis, aucune condition n'est requise ici selon le scénario. – nas

+0

Puisque les messages ont des catégories, la logique de @ Vincent n'a pas à gérer les scénarios de catégories sans posts. Il est préférable de filtrer les catégories vides au niveau de la base de données en ajoutant la condition supplémentaire. –

Questions connexes