views:

193

answers:

2

In Rails, to automatically count associations, you do:

class Script
  has_many: chapters
end
class Chapter
  belongs_to :script
end

and you add a Chapters_count column into the Script model.

Now, what if you want to count the number of paragraphs in a Script *without having a script_id key in the paragraph model ?*

class Script
  has_many: chapters
  has_many: paragraphs # not complete
end
class Chapter
  has_many: paragraphs
  belongs_to :script
end
class Paragraph
  belongs_to :chapter
end

How do you automatically associate script to paragraph and count them using the automatic count of Rails ?

A: 

You're on the right track. But first you've got to address a small error. Rails won't update a counter cache unless you instruct it to.

class Chapter
  belongs_to :script, :counter_cache => true
end

Will automatically update @script.chapter_count before creation and after destruction of all associated Chapters.

Unfortunately things aren't so simply when dealing :through relationships. You will need to update the associated script's paragraph counter through callbacks in the Paragraph model.

N.B.: The following assumes you want to keep a paragraph counter in Chapter as well.

Start by applying the same theory to the Chapter model, and a paragraphs count column to the Script table.

class PrepareForCounterCache < ActiveRecord::Migration
  def self.up
    add_column :scripts, :paragraphs_count, :integer, :default => 0
    add_column :chapters, :paragraphs_count, :integer, :default => 0

    Chapter.reset_column_information
    Script.reset_column_information

    Chapter.find(:all).each do |c|
      paragraphs_count = c.paragraphs.length
      Chapter.update_counters c.id, :paragraphs_count => paragraphs_count
      Script.update_counters c.script_id, :paragraphs_count => paragraphs_count
    end
  end
  def self.down
    remove_column :scripts, :paragraphs_count
    remove_column :chapters, :paragraphs_count
  end
end

Now to set up the relationships:

class Script
  has_many: chapters
  has_many: paragraphs, :through => :chapters
end

class Chapter
  has_many: paragraphs
  belongs_to :script, :counter_cache => true
end

class Paragraph
  belongs_to :chapter, :counter_cache => true
end

All that's left is to tell Paragraph to update the paragraph counters in script as a callback.

class Paragraph < ActiveRecord::Base
  belongs_to :chapter, :counter_cache => true
  before_save :increment_script_paragraph_count
  after_destroy, :decrement_script_paragraph_count

  protected
  def increment_script_paragraph_count
    Script.update_counters chapter.script_id, :paragaraphs_count => 1
  end
  def decrement_script_paragraph_count
    Script.update_counters chapter.script_id, :paragaraphs_count => -1
  end
end
EmFi
A: 

The quick and simple way, without using a cache is to do:

class Script
  has_many :chapters
  has_many :paragraphs, :through => :chapters
end

script = Script.find(1)
puts script.paragraphs.size #get the count
MattMcKnight
That's strange, I thought the transitive association didn't work because script.paragraphs.size is egal to 0. But script.paragraphs.length is correct.But I would really like tiouse paragraphs_count, the automatic Rails count.
MickTaiwan
This is a rails feature (a method added by has_many). counter_cache doesn't work on a has_many :through, because the cache is implemented on the belongs_to side. You'd have to write your own, here's an example: http://www.ruby-forum.com/topic/138094#614246
MattMcKnight