association_collection.rb 3.9 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
module ActiveRecord
  module Associations
    class AssociationCollection #:nodoc:
      alias_method :proxy_respond_to?, :respond_to?
      instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?)/ }

      def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
        @owner = owner
        @options = options
        @association_name = association_name
        @association_class = eval(association_class_name)
        @association_class_primary_key_name = association_class_primary_key_name
      end
      
      def method_missing(symbol, *args, &block)
        load_collection
        @collection.send(symbol, *args, &block)
      end
  
      def to_ary
        load_collection
        @collection.to_ary
      end
  
25 26
      def respond_to?(symbol, include_priv = false)
        proxy_respond_to?(symbol, include_priv) || [].respond_to?(symbol, include_priv)
D
Initial  
David Heinemeier Hansson 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39
      end

      def loaded?
        !@collection.nil?
      end
      
      def reload
        @collection = nil
      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)
40 41 42 43 44 45
        @owner.transaction do
          flatten_deeper(records).each do |record|
            raise_on_type_mismatch(record)
            insert_record(record)
            @collection << record if loaded?
          end
D
Initial  
David Heinemeier Hansson 已提交
46
        end
47

D
Initial  
David Heinemeier Hansson 已提交
48 49 50 51 52 53 54 55 56
        self
      end

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

      # Remove +records+ from this association.  Does not destroy +records+.
      def delete(*records)
        records = flatten_deeper(records)
57 58 59 60 61 62
        
        @owner.transaction do
          records.each { |record| raise_on_type_mismatch(record) }
          delete_records(records)
          records.each { |record| @collection.delete(record) } if loaded?
        end
D
Initial  
David Heinemeier Hansson 已提交
63 64 65
      end
      
      def destroy_all
66 67 68 69
        @owner.transaction do
          each { |record| record.destroy }
        end

D
Initial  
David Heinemeier Hansson 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
        @collection = []
      end
      
      def size
        if loaded? then @collection.size else count_records end
      end
      
      def empty?
        size == 0
      end
      
      def uniq(collection = self)
        collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
      end
      
      alias_method :length, :size

      protected
        def loaded?
          not @collection.nil?
        end

        def quoted_record_ids(records)
93
          records.map { |record| record.quoted_id }.join(',')
D
Initial  
David Heinemeier Hansson 已提交
94 95 96 97 98 99 100 101 102
        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
103 104 105 106 107 108 109 110 111

        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

D
Initial  
David Heinemeier Hansson 已提交
112 113
      private
        def load_collection
114 115 116 117 118 119 120 121
          if loaded?
            @collection
          else
            begin
              @collection = find_all_records
            rescue ActiveRecord::RecordNotFound
              @collection = []
            end
D
Initial  
David Heinemeier Hansson 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134
          end
        end

        def raise_on_type_mismatch(record)
          raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
        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
135
end