has_many_association.rb 4.7 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2
module ActiveRecord
  module Associations
P
Pratik Naik 已提交
3 4 5 6
    # This is the proxy that handles a has many association.
    #
    # If the association has a <tt>:through</tt> option further specialization
    # is provided by its child HasManyThroughAssociation.
D
Initial  
David Heinemeier Hansson 已提交
7
    class HasManyAssociation < AssociationCollection #:nodoc:
8 9 10 11
      def initialize(owner, reflection)
        @finder_sql = nil
        super
      end
D
Initial  
David Heinemeier Hansson 已提交
12
      protected
13 14 15 16 17 18 19 20
        def owner_quoted_id
          if @reflection.options[:primary_key]
            quote_value(@owner.send(@reflection.options[:primary_key]))
          else
            @owner.quoted_id
          end
        end

P
Pratik Naik 已提交
21 22 23
        # Returns the number of records in this collection.
        #
        # If the association has a counter cache it gets that value. Otherwise
24 25 26 27
        # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
        # there's one.  Some configuration options like :group make it impossible
        # to do a SQL count, in those cases the array count will be used.
        #
P
Pratik Naik 已提交
28 29 30 31 32 33
        # That does not depend on whether the collection has already been loaded
        # or not. The +size+ method is the one that takes the loaded flag into
        # account and delegates to +count_records+ if needed.
        #
        # If the collection is empty the target is set to an empty array and
        # the loaded flag is set to true as well.
D
Initial  
David Heinemeier Hansson 已提交
34
        def count_records
35
          count = if has_cached_counter?
D
Initial  
David Heinemeier Hansson 已提交
36
            @owner.send(:read_attribute, cached_counter_attribute_name)
37 38
          elsif @reflection.options[:counter_sql]
            @reflection.klass.count_by_sql(@counter_sql)
D
Initial  
David Heinemeier Hansson 已提交
39
          else
40
            @reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
D
Initial  
David Heinemeier Hansson 已提交
41
          end
42 43 44 45 46

          # If there's nothing in the database and @target has no new records
          # we are certain the current target is an empty array. This is a
          # documented side-effect of the method that may avoid an extra SELECT.
          @target ||= [] and loaded if count == 0
47

48 49 50
          if @reflection.options[:limit]
            count = [ @reflection.options[:limit], count ].min
          end
51

52
          return count
D
Initial  
David Heinemeier Hansson 已提交
53
        end
54

D
Initial  
David Heinemeier Hansson 已提交
55 56 57
        def has_cached_counter?
          @owner.attribute_present?(cached_counter_attribute_name)
        end
58

D
Initial  
David Heinemeier Hansson 已提交
59
        def cached_counter_attribute_name
60
          "#{@reflection.name}_count"
D
Initial  
David Heinemeier Hansson 已提交
61 62
        end

63
        def insert_record(record, force = false, validate = true)
64
          set_belongs_to_association_for(record)
65
          force ? record.save! : record.save(:validate => validate)
D
Initial  
David Heinemeier Hansson 已提交
66 67
        end

68
        # Deletes the records according to the <tt>:dependent</tt> option.
D
Initial  
David Heinemeier Hansson 已提交
69
        def delete_records(records)
70 71
          case @reflection.options[:dependent]
            when :destroy
72
              records.each { |r| r.destroy }
73
            when :delete_all
74
              @reflection.klass.delete(records.map { |record| record.id })
75
            else
76
              relation = Arel::Table.new(@reflection.table_name)
77
              relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
J
Jeremy Kemper 已提交
78
                  and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
79 80
              ).update(relation[@reflection.primary_key_name] => nil)

81
              @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
82
          end
D
Initial  
David Heinemeier Hansson 已提交
83
        end
84 85 86 87 88 89

        def target_obsolete?
          false
        end

        def construct_sql
90
          case
91 92 93 94
            when @reflection.options[:finder_sql]
              @finder_sql = interpolate_sql(@reflection.options[:finder_sql])

            when @reflection.options[:as]
95
              @finder_sql =
96
                "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
97
                "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
98
              @finder_sql << " AND (#{conditions})" if conditions
99

100
            else
101
              @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
102
              @finder_sql << " AND (#{conditions})" if conditions
103 104
          end

105
          construct_counter_sql
106
        end
107 108 109 110

        def construct_scope
          create_scoping = {}
          set_belongs_to_association_for(create_scoping)
111
          {
112
            :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
113 114
            :create => create_scoping
          }
115
        end
116 117 118 119 120

        def we_can_set_the_inverse_on_this?(record)
          inverse = @reflection.inverse_of
          return !inverse.nil?
        end
D
Initial  
David Heinemeier Hansson 已提交
121 122 123
    end
  end
end