aboutsummaryrefslogblamecommitdiffstats
path: root/activerecord/lib/active_record/associations/association_collection.rb
blob: 334cdccc3ddcca40cff448e6c2df65dc88dcf9e9 (plain) (tree)
1
2
3
4
5
6
7
8

                     
                                                           
                

                      

         


                       

         
                
             




                                                                                                            

                     


                                                  

                                                                      
             
           
 
                       







                                                                            


                                                                                 

                             
                                 
                                                          
           


                     



                                          
                    

         






                                                                                   


                                                                                                                             
              
                                                           

         


                                                                                                                               
                        

         
                
                  




                                                                                                                                  
 





                                                             
 
               
                                      
                                                             








                                                                            








                                                        
             



                                                                                                                                                    



                            





                                                                                                                        
   
module ActiveRecord
  module Associations
    class AssociationCollection < AssociationProxy #:nodoc:
      def to_ary
        load_target
        @target.to_ary
      end
  
      def reset
        @target = []
        @loaded = false
      end

      def reload
        reset
      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
        @owner.transaction do
          flatten_deeper(records).each do |record|
            raise_on_type_mismatch(record)
            result &&= insert_record(record) unless @owner.new_record?
            @target << record
          end
        end

        result and self
      end

      alias_method :push, :<<
      alias_method :concat, :<<

      # Remove +records+ from this association.  Does not destroy +records+.
      def delete(*records)
        records = flatten_deeper(records)
        records.each { |record| raise_on_type_mismatch(record) }
        records.reject! { |record| @target.delete(record) if record.new_record? }
        return if records.empty?
        
        @owner.transaction do
          delete_records(records)
          records.each { |record| @target.delete(record) }
        end
      end
      
      def destroy_all
        @owner.transaction do
          each { |record| record.destroy }
        end

        @target = []
      end
      
      def create(attributes = {})
        # Can't use Base.create since the foreign key may be a protected attribute.
        record = build(attributes)
        record.save unless @owner.new_record?
        record
      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 loaded? then @target.size else count_records end
      end
      
      # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
      # whether the collection is empty, use collection.length.zero? instead of collection.empty?
      def length
        load_target.size
      end
      
      def empty?
        size.zero?
      end
      
      def uniq(collection = self)
        collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
      end

      def replace(other_array)
        other_array.each{ |val| raise_on_type_mismatch(val) }

        @target = other_array
        @loaded = true
      end

      protected
        def quoted_record_ids(records)
          records.map { |record| record.quoted_id }.join(',')
        end

        def interpolate_sql_options!(options, *keys)
          keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
        end

        def interpolate_sql(sql, record = nil)
          @owner.send(:interpolate_sql, sql, record)
        end

        def sanitize_sql(sql)
          @association_class.send(:sanitize_sql, sql)
        end

        def extract_options_from_args!(args)
          @owner.send(:extract_options_from_args!, args)
        end

      private
        def raise_on_type_mismatch(record)
          raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
        end

        def target_obsolete?
          false
        end

        # Array#flatten has problems with rescursive arrays. Going one level deeper solves the majority of the problems.
        def flatten_deeper(array)
          array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
        end
    end
  end
end