ActiveRecord で関連レコードの size をプリロードする
テーブルの一覧表示で関連レコードの数を表示したい場合、
View でそのまま records[i].subrecords.size などとして繰り返し参照すると SELECT count(*) ...
というようなカウント SQL が大量に実行されてしまう。
以下を利用するとカウント SQL の実行を1回にできる。
使い方
records = Record.find(:all, :include_count => :subrecords) records[0].subrecords.size # カウント SQL が実行されない
lib/active_record_include_count.rb
require 'active_record' class ActiveRecord::Base VALID_FIND_OPTIONS << :include_count def self.find(*args) options = args.extract_options! validate_find_options(options) set_readonly_option!(options) records = case args.first when :first then find_initial(options) when :last then find_last(options) when :all then find_every(options) else find_from_ids(args, options) end if options.has_key?(:include_count) Array(options[:include_count]).each do |association| preload_sizes(records, association) end end records end private def self.preload_sizes(records, association) class_to_reflection = {} records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records| raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection sizes = {} ids = records.map {|record| record.quoted_id}.join(',') reflection.klass.count(:conditions => "#{reflection.primary_key_name} IN (#{ids})", :group => reflection.primary_key_name ).map {|record| sizes[record[0]] = record[1]} records.each do |record| unless sizes[record.id] record.send(association).loaded else record["#{reflection.name}_count"] = sizes[record.id] end end end end end
config/initializers/active_record.rb
require 'active_record_include_count'