has_many_association.rb 4.4 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActiveRecord
2
  # = Active Record Has Many Association
D
Initial  
David Heinemeier Hansson 已提交
3
  module Associations
P
Pratik Naik 已提交
4 5 6 7
    # 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 已提交
8 9
    class HasManyAssociation < AssociationCollection #:nodoc:
      protected
10 11
        def owner_quoted_id
          if @reflection.options[:primary_key]
12
            @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
13 14 15 16 17
          else
            @owner.quoted_id
          end
        end

P
Pratik Naik 已提交
18 19 20
        # Returns the number of records in this collection.
        #
        # If the association has a counter cache it gets that value. Otherwise
21 22
        # 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
23
        # to do an SQL count, in those cases the array count will be used.
24
        #
P
Pratik Naik 已提交
25 26 27 28 29 30
        # 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 已提交
31
        def count_records
32
          count = if has_cached_counter?
D
Initial  
David Heinemeier Hansson 已提交
33
            @owner.send(:read_attribute, cached_counter_attribute_name)
34 35
          elsif @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
            @reflection.klass.count_by_sql(custom_counter_sql)
D
Initial  
David Heinemeier Hansson 已提交
36
          else
37
            @reflection.klass.count(@scope[:find].slice(:conditions, :joins, :include))
D
Initial  
David Heinemeier Hansson 已提交
38
          end
39 40 41 42 43

          # 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
44

A
Aaron Patterson 已提交
45
          [@reflection.options[:limit], count].compact.min
D
Initial  
David Heinemeier Hansson 已提交
46
        end
47

D
Initial  
David Heinemeier Hansson 已提交
48 49 50
        def has_cached_counter?
          @owner.attribute_present?(cached_counter_attribute_name)
        end
51

D
Initial  
David Heinemeier Hansson 已提交
52
        def cached_counter_attribute_name
53
          "#{@reflection.name}_count"
D
Initial  
David Heinemeier Hansson 已提交
54 55
        end

56
        def insert_record(record, force = false, validate = true)
57
          set_belongs_to_association_for(record)
58
          force ? record.save! : record.save(:validate => validate)
D
Initial  
David Heinemeier Hansson 已提交
59 60
        end

61
        # Deletes the records according to the <tt>:dependent</tt> option.
D
Initial  
David Heinemeier Hansson 已提交
62
        def delete_records(records)
63 64
          case @reflection.options[:dependent]
            when :destroy
65
              records.each { |r| r.destroy }
66
            when :delete_all
67
              @reflection.klass.delete(records.map { |record| record.id })
68
            else
69
              relation = Arel::Table.new(@reflection.table_name)
70
              relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
71
                  and(relation[@reflection.klass.primary_key].in(records.map { |r| r.id }))
72 73
              ).update(relation[@reflection.primary_key_name] => nil)

74
              @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
75
          end
D
Initial  
David Heinemeier Hansson 已提交
76
        end
77 78 79 80 81

        def target_obsolete?
          false
        end

82 83 84 85 86 87 88
        def construct_conditions
          if @reflection.options[:as]
            sql =
              "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
              "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
          else
            sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
89
          end
90 91 92
          sql << " AND (#{conditions})" if conditions
          sql
        end
93

94 95 96 97 98 99 100 101
        def construct_find_scope
          {
            :conditions => construct_conditions,
            :readonly   => false,
            :order      => @reflection.options[:order],
            :limit      => @reflection.options[:limit],
            :include    => @reflection.options[:include]
          }
102
        end
103

104
        def construct_create_scope
105 106
          create_scoping = {}
          set_belongs_to_association_for(create_scoping)
107
          create_scoping
108
        end
109 110

        def we_can_set_the_inverse_on_this?(record)
111
          !@reflection.inverse_of.nil?
112
        end
D
Initial  
David Heinemeier Hansson 已提交
113 114 115
    end
  end
end