Counting

Hey,

Pretty much a follow up to my previous thread on here but thought it warranted it’s own topic.

Background:

I have 3 models:

Collections
  has_many collections as children
  belongs_to collection as parent


Albums
  belongs_to collection
  has_many photos


Photos
  belongs_to album

Basically I want to write an instance method for a collection to return the total number of photos of ALL children albums including all those albums in children collections of the calling collection.

So for example:

Collection 1
  Collection 2
    Album 1 - 5 photos
    Album 2 - 6 photos
  Collection 3
    Album 3 - 10 photos

Calling photo_count on collection 1 would return 21 and calling it on collection 2 would return 11

I have tried to write it as follows but it’s not quite right, I realise I need to use a try method somewhere but i’m just not sure how to finish it off.

  def photo_count
    count = 0
    albums.each do |album|
      count += album.photo_count
    end
  end

Could anyone give me a hand here?

Neil

Hi Neil,

I’d be looking for gems to solve this, checkout ancestry which will let you do albums.descendants.count

But, you can probably do something like this if you do want to code it yourself.
http://www.dzone.com/snippets/find-all-children-actsastree

The tree gems would work fine but only saw those after I had started building it manually which actually wasn’t that difficult in the first place. It’s not actually the model that is being the tree I want to query, It’t the items at the end of the branches I want information from.

In my app, the collections act as a tree and the albums just sit at the ends, each albums just has a parent collection id to show what collection it belongs to. Please see the collection/album hierarchy I posted above, sorry, it may have seemed a bit confusing but I need to be able to find out how many photos are in every collection but just unsure how to put everything together to get the right results.

Neil

It does seem like one of your models isn’t necessary.
I would have gone with something like this and the ancestry gem.


Album 1
  Album 2
    Album 3 - 5 photos
    Album 4 - 6 photos
  Album 5
    Album 6 - 10 photos

I’d suggest looking at those gems again and implementing one of the their searching algorithms for getting all the descendants.
It’s the recursive tree part that is tripping you up, once you have that it’s easy.


Class Collection < ActiveRecord::Base

  def photo_count
    albums = descendants.collect! { |x| x.albums }
    albums.collect! { |x| x.photos }.length
  end

end

Or if you can make collections have many photos through albums you could just use
descendants.collect! { |x| x.photos }.length

This code is not far off:

def photo_count
  count = 0
  albums.each do |album|
    count += album.photo_count
  end
end

If you return the count at the end it should work, i.e:

def photo_count
  count = 0
  albums.each do |album|
    count += album.photo_count
  end
  count
end

This is a common programming pattern, and there’s a much shorter way of writing it:

def photo_count
  albums.inject(0) { |sum, album| sum + album.photo_count }
end

A bit like using “each” instead of writing for loops by hand, much easier!

The ultra concise version would be this:

def photo_count
  albums.map(&:photo_count).inject(0, &:+)
end

Whether that’s easier or harder to understand than the original depends on what you’re used to :slight_smile:

Cheers,
Tim

Just curious: Have you looked into using :counter_cache for doing this?