require 'set' module ActiveRecord module Associations class AssociationCollection < AssociationProxy #:nodoc: def find(*args) expects_array = args.first.kind_of?(Array) ids = args.flatten.compact.uniq.map(&:to_i) if @reflection.options[:cached] result = @owner.send(:cache_read, @reflection) if result result = result.select { |record| ids.include? record.id } result = expects_array ? result : result.first return result end end options = args.extract_options! # If using a custom finder_sql, scan the entire collection. if @reflection.options[:finder_sql] if ids.size == 1 id = ids.first record = load_target.detect { |r| id == r.id } expects_array ? [ record ] : record else load_target.select { |r| ids.include?(r.id) } end else conditions = "#{@finder_sql}" if sanitized_conditions = sanitize_sql(options[:conditions]) conditions << " AND (#{sanitized_conditions})" end options[:conditions] = conditions if options[:order] && @reflection.options[:order] options[:order] = "#{options[:order]}, #{@reflection.options[:order]}" elsif @reflection.options[:order] options[:order] = @reflection.options[:order] end # Build options specific to association construct_find_options!(options) merge_options_from_reflection!(options) # Pass through args exactly as we received them. args << options @reflection.klass.find(*args) end end # Add +records+ to this association. Returns +self+ so method calls may be chained. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically. def <<(*records) result = true load_target if @owner.new_record? @owner.transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) add_record_to_target_with_callbacks(record) do |r| result &&= insert_record(record) unless @owner.new_record? end end end @owner.send(:cache_write, @reflection, self) if @reflection.options[:cached] result && self end # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length. def size if @reflection.options[:cached] result = @owner.send(:cache_read, @reflection) return result.to_ary.size if result end if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) @target.size elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) unsaved_records = @target.select { |r| r.new_record? } unsaved_records.size + count_records else count_records end end end end end