views:

60

answers:

4

Let's say I have two models Post and Category:

class Post < ActiveRecord::Base
  belongs_to :category
end

class Category < ActiveRecord::Base
  has_many :posts
end

Is there a method that will allow me to do something like

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]

In other words, 'split' the collection of all posts into arrays by selected category ids

A: 

Sure but given your model relationships you I think you need to look at it the other way around.

p = []
1.upto(some_limit) do |n|
  posts = Category.posts.find_by_id(n)
  p.push posts if posts
end
bjg
Yes, but this way it will run some_limit SQL queries and I would like to use just one query (posts = Post.find(:all)) if possible.
Vincent
A: 

Something like this might work (instance method of Post, untested):

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
+4  A: 

Try the group_by function on Array class:

posts.group_by(&:category_id)

Refer to the API documentation for more details.

Caveat:

Grouping should not performed in the Ruby code when the potential dataset can be big. I use the group_by function when the max possible dataset size is < 1000. In your case you might have 1000s of Posts. Processing such an array will put strain on your resources. Rely on the database to perform the grouping/sorting/aggregation etc.

Here is one way to do it(similar solution is suggested by 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
KandadaBoggu
The 'group_by' solution is exactly what I was looking for, thanks!
Vincent
A: 

Instead of getting all posts and then doing some operation on them to categorize them which is a bit performance intensive exercise I would rather prefer to use eager loading like so

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

This will generate one sql query to fetch all your posts and category objects. Then you can easily iterate over them:

p = Array.new
categories.each do |category| 
  p[1] = category.posts
  # do anything with p[1] array of posts for the category
end
nas
@nas this will return even the categories with out posts. You have to add additional conditions to filter the categories without posts.
KandadaBoggu
@KandadaBoggu I dont think it matters whether you get categories without posts or not because according to the question @Vincent is doing operations on posts not on categories. And when you will call `category.posts` then you will get an empty array and iterating through an empty array wont execute your code block hence no effect. So IMHO no conditions are required here according to scenario.
nas
Since posts have categories, @Vincent's logic does not have to handle the scenarios of categories with out posts. It's better to filter out the empty categories at the DB level by adding the additional condition.
KandadaBoggu
@KandadaBoggu I am sorry, posts do not have categories; categories have posts and posts belong to categories. Why do you think @Vincent's logic would have to handle the scenarios of categories without posts? Could you please read my previous comment again.
nas
The `posts` table has a column called called `category_id`. Each post has a category and a category might OR might not have posts. My point is to eliminate the empty categories in the DB query rather than in the ruby code. This will reduce the categories result-set size returned by the SQL and make the lazy fetch queries faster.
KandadaBoggu
This becomes relevant when you have pagination. Lets say your query is returning the first page of categories and if you eliminate the empty categories from this page at the ruby level, you will have uneven page size while displaying the paginated result.
KandadaBoggu
@KandadaBoggu I just give up now....
nas