1

So, I have a Folder and a FolderItem models.

UPDATE

# == Schema Information
#
# Table name: folders
#
#  id                 :integer         not null, primary key
#  name               :string(255)     not null
#  parent_folder_id   :integer
#  user_id            :integer         not null
#  folder_itens_count :integer         default(0)
#  created_at         :datetime
#  updated_at         :datetime
#

class Folder < ActiveRecord::Base
...

  belongs_to :parent_folder, :class_name => 'Folder'
  has_many :child_folders, :class_name => 'Folder', :foreign_key => :parent_folder_id
  has_many :folder_itens, :order => 'created_at DESC'

  after_update {
    update_parent_folder_itens_count
  }

  def update_parent_folder_itens_count
    parent_folder = self.parent_folder
    if self.folder_itens_count_changed? && parent_folder
      quant_changed = self.folder_itens_count - self.folder_itens_count_was
      parent_folder.increment(:folder_itens_count, quant_changed)
    end
  end
end

class FolderItem < ActiveRecord::Base
... 
  belongs_to :folder, :counter_cache => :folder_itens_count
end

I'm using a counter_cache to keep the number of itens of a single folder. But a folder might be the parent of another folder, and I wanted the parent folder to have the sum of counter_cache of all it's children plus it's own counter_cache.

To do so I tried to put a after_update method caching the changes made in the counter_cache column, but somehow this method is not being called when a new FolderItem is created.

  • Some actual code to see why it is not called would be great. Like the FolderItem class? It sounds like something you're going to have to write full custom caching code for though, because counter_cache wasn't made for anything else than Folder.has_many :items. What you're doing is Folder.has_many :folders, which on itself has many folders. – tombruijn Feb 22 '12 at 17:19
  • Just added the code. I still think what I did should be possible, but I think it doesn't work because of the way the counter_cache is handled, I guess it is like a nested nested SQL, so the rails code is ignored. But I just want to be sure. – Raphael Melo Feb 22 '12 at 17:38

1 Answers1

1

I'd do something like this.

Add some cache counter fields to folders table

$ rails g migration add_cache_counters_to_folders child_folders_count:integer \
                                                  folder_items_count:integer  \
                                                  total_items_count:integer   \
                                                  sum_of_children_count:integer

And Ruby code

class Folder < ActiveRecord::Base
  belongs_to :parent_folder, class_name: 'Folder', counter_cache: :child_folders_count
  has_many :child_folders, class_name: 'Folder', foreign_key: :parent_folder_id
  has_many :folder_items

  before_save :cache_counters

  # Direct descendants - files and items within this folder
  def total_items
    child_folders_count + folder_items_count
  end

  # All descendants - files and items within all the folders in this folder
  def sum_of_children
    folder_items_count + child_folders.map(&:sum_of_children).inject(:+)
  end

private
  def cache_counters
    self.total_items_count = total_items
    self.sum_of_children_count = sum_of_children
  end
end

class FolderItem < ActiveRecord::Base
  belongs_to :folder, counter_cache: true # folder_items_count
end

Please note that the Folder#sum_of_children-method is recursive, so for large datasets it may slow down your application. You may want to do some more SQL-magic to it, but as pure Ruby this is as close to a good solution as I can get. I saw you did it the other way around, that will be just as slow as you need to update from the bottom-up as well. (This is top-down)

Don't know if that's what you're looking for, but it's a readable solution for caching the number of items within a folder.

theodorton
  • 674
  • 1
  • 6
  • 11