has_many_association.rb 4.9 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2 3 4 5
module ActiveRecord
  module Associations
    class HasManyAssociation < AssociationCollection #:nodoc:
      def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
        super(owner, association_name, association_class_name, association_class_primary_key_name, options)
6
        @conditions = sanitize_sql(options[:conditions])
D
Initial  
David Heinemeier Hansson 已提交
7 8 9 10

        if options[:finder_sql]
          @finder_sql = interpolate_sql(options[:finder_sql])
        else
11
          @finder_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}"
12
          @finder_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
13 14 15 16 17
        end

        if options[:counter_sql]
          @counter_sql = interpolate_sql(options[:counter_sql])
        elsif options[:finder_sql]
18 19
          options[:counter_sql] = options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
          @counter_sql = interpolate_sql(options[:counter_sql])
20
        else
21
          @counter_sql = "#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@conditions ? " AND " + interpolate_sql(@conditions) : ""}"
D
Initial  
David Heinemeier Hansson 已提交
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
        end
      end

      def create(attributes = {})
        # Can't use Base.create since the foreign key may be a protected attribute.
        record = build(attributes)
        record.save
        @collection << record if loaded?
        record
      end

      def build(attributes = {})
        record = @association_class.new(attributes)
        record[@association_class_primary_key_name] = @owner.id
        record
      end

39 40 41
      def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
        if @options[:finder_sql]
          records = @association_class.find_by_sql(@finder_sql)
D
Initial  
David Heinemeier Hansson 已提交
42
        else
43 44 45 46
          sql = @finder_sql
          sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
          orderings ||= @options[:order]
          records = @association_class.find_all(sql, orderings, limit, joins)
D
Initial  
David Heinemeier Hansson 已提交
47 48 49
        end
      end

50 51 52 53 54 55 56 57 58 59 60
      # Count the number of associated records. All arguments are optional.
      def count(runtime_conditions = nil)
        if @options[:finder_sql]
          @association_class.count_by_sql(@finder_sql)
        else
          sql = @finder_sql
          sql << " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
          @association_class.count(sql)
        end
      end
      
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
      # Find the first associated record.  All arguments are optional.
      def find_first(conditions = nil, orderings = nil)
        find_all(conditions, orderings, 1).first
      end

      def find(*args)
        # Return an Array if multiple ids are given.
        expects_array = args.first.kind_of?(Array)

        ids = args.flatten.compact.uniq

        # If no ids given, raise RecordNotFound.
        if ids.empty?
          raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"

        # If using a custom finder_sql, scan the entire collection.
        elsif @options[:finder_sql]
          if ids.size == 1
            id = ids.first
            record = load_collection.detect { |record| id == record.id }
            expects_array? ? [record] : record
          else
            load_collection.select { |record| ids.include?(record.id) }
          end

        # Otherwise, delegate to association class with conditions.
D
Initial  
David Heinemeier Hansson 已提交
87
        else
88
          args << { :conditions => "#{@association_class_primary_key_name} = #{@owner.quoted_id} #{@conditions ? " AND " + @conditions : ""}" }
89
          @association_class.find(*args)
D
Initial  
David Heinemeier Hansson 已提交
90 91 92 93 94 95
        end
      end

      # Removes all records from this association.  Returns +self+ so
      # method calls may be chained.
      def clear
96
        @association_class.update_all("#{@association_class_primary_key_name} = NULL", "#{@association_class_primary_key_name} = #{@owner.quoted_id}")
D
Initial  
David Heinemeier Hansson 已提交
97 98 99 100 101 102
        @collection = []
        self
      end

      protected
        def find_all_records
103
          find_all
D
Initial  
David Heinemeier Hansson 已提交
104
        end
105

D
Initial  
David Heinemeier Hansson 已提交
106 107 108
        def count_records
          if has_cached_counter?
            @owner.send(:read_attribute, cached_counter_attribute_name)
109
          elsif @options[:counter_sql]
D
Initial  
David Heinemeier Hansson 已提交
110 111 112 113 114
            @association_class.count_by_sql(@counter_sql)
          else
            @association_class.count(@counter_sql)
          end
        end
115

D
Initial  
David Heinemeier Hansson 已提交
116 117 118
        def has_cached_counter?
          @owner.attribute_present?(cached_counter_attribute_name)
        end
119

D
Initial  
David Heinemeier Hansson 已提交
120 121 122 123 124 125 126 127 128 129
        def cached_counter_attribute_name
          "#{@association_name}_count"
        end

        def insert_record(record)
          record.update_attribute(@association_class_primary_key_name, @owner.id)
        end

        def delete_records(records)
          ids = quoted_record_ids(records)
130 131 132 133
          @association_class.update_all(
            "#{@association_class_primary_key_name} = NULL", 
            "#{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_class.primary_key} IN (#{ids})"
          )
D
Initial  
David Heinemeier Hansson 已提交
134 135 136 137
        end
    end
  end
end