views:

32

answers:

2

I have two models:

Program < ActiveRecord::Base
  has_many :events

  def federal_financing
    events.sum(&:federal_financing)
  end

  def regional_financing
    events.sum(&:regional_financing)
  end

  def local_financing
    events.sum(&:local_financing)
  end
end

Event < ActiveRecord::Base
  belongs_to :program
  # events table have this decimal fields: federal_financing, local_financing, regional_financing
end

Always if I call one of these three methods I'm calling another ones. So I want to avoid of loading events each time I call any of these methods. Current solution now is to define

def after_initialize
  @all_events = events
end

and use @all_events instead of events in methods. BUT I do not want to load events when object loads - I want "cache" events only if any of these three methods was called and other methods should use cached version of events.

+1  A: 

I believe you can use the following to load events just once per program:

Program < ActiveRecord::Base
  has_many :events

  def federal_financing
    all_events.sum(&:federal_financing)
  end

  def regional_financing
    all_events.sum(&:regional_financing)
  end

  def local_financing
    all_events.sum(&:local_financing)
  end

  def all_events
    # if @events is not initialized yet or is nil or is false, 
    #    self.events will be loaded. otherwise @events will remain unchanged.
    @events ||= self.events 
  end
end
j.
A: 

Rails will cache the query that loads the events automatically, so there's no need for you to explicitly cache the events. However, for this to work you'll need to convert the events to an array before calculating the sum; events.sum means that you want to construct an SQL query to calculate the sum, and won't work.

Change your Program model as follows:

Program < ActiveRecord::Base
  has_many :events

  def federal_financing
    events.to_a.sum(&:federal_financing)
  end

  def regional_financing
    events.to_a.sum(&:regional_financing)
  end

  def local_financing
    events.to_a.sum(&:local_financing)
  end
end

Now, by enabling ActiveRecord logging in the console, it's easy to see that the queries indeed get cached:

$ script/console
>> ActiveRecord::Base.logger = Logger.new(STDOUT)
>> p = Program.first
  Program Load (1.0ms)   SELECT * FROM "programs" LIMIT 1
=> #<Program id: 1, created_at: "2010-06-08 15:01:08", updated_at: "2010-06-08 15:01:08">
>> p.federal_financing
  Event Load (1.0ms)   SELECT * FROM "events" WHERE ("events".program_id = 1) 
=> 147
>> p.regional_financing
=> 23
>> p.local_financing
=> 11

As you can see, the events are loaded only once, during the first call to federal_financing.

Pär Wieslander