Skip to content
This repository
tag: v3.1.0.rc4
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 147 lines (128 sloc) 4.982 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
module ActiveRecord
  module Associations
    # Association proxies in Active Record are middlemen between the object that
    # holds the association, known as the <tt>@owner</tt>, and the actual associated
    # object, known as the <tt>@target</tt>. The kind of association any proxy is
    # about is available in <tt>@reflection</tt>. That's an instance of the class
    # ActiveRecord::Reflection::AssociationReflection.
    #
    # For example, given
    #
    # class Blog < ActiveRecord::Base
    # has_many :posts
    # end
    #
    # blog = Blog.find(:first)
    #
    # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
    # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
    # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
    #
    # This class has most of the basic instance methods removed, and delegates
    # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
    # corner case, it even removes the +class+ method and that's why you get
    #
    # blog.posts.class # => Array
    #
    # though the object behind <tt>blog.posts</tt> is not an Array, but an
    # ActiveRecord::Associations::HasManyAssociation.
    #
    # The <tt>@target</tt> object is not \loaded until needed. For example,
    #
    # blog.posts.count
    #
    # is computed directly through SQL and does not trigger by itself the
    # instantiation of the actual post records.
    class CollectionProxy # :nodoc:
      alias :proxy_extend :extend

      instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }

      delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
               :lock, :readonly, :having, :to => :scoped

      delegate :target, :load_target, :loaded?, :scoped,
               :to => :@association

      delegate :select, :find, :first, :last,
               :build, :create, :create!,
               :concat, :delete_all, :destroy_all, :delete, :destroy, :uniq,
               :sum, :count, :size, :length, :empty?,
               :any?, :many?, :include?,
               :to => :@association

      def initialize(association)
        @association = association
        Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
      end

      alias_method :new, :build

      def respond_to?(*args)
        super ||
        (load_target && target.respond_to?(*args)) ||
        @association.klass.respond_to?(*args)
      end

      def method_missing(method, *args, &block)
        match = DynamicFinderMatch.match(method)
        if match && match.instantiator?
          record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
            @association.send :set_owner_attributes, r
            @association.send :add_to_target, r
            yield(r) if block_given?
          end
        end

        if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
          if load_target
            if target.respond_to?(method)
              target.send(method, *args, &block)
            else
              begin
                super
              rescue NoMethodError => e
                raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
              end
            end
          end

        else
          scoped.readonly(nil).send(method, *args, &block)
        end
      end

      # Forwards <tt>===</tt> explicitly to the \target because the instance method
      # removal above doesn't catch it. Loads the \target if needed.
      def ===(other)
        other === load_target
      end

      def to_ary
        load_target.dup
      end
      alias_method :to_a, :to_ary

      def <<(*records)
        @association.concat(records) && self
      end
      alias_method :push, :<<

      def clear
        delete_all
        self
      end

      def reload
        @association.reload
        self
      end

      def proxy_owner
        ActiveSupport::Deprecation.warn(
          "Calling record.#{@association.reflection.name}.proxy_owner is deprecated. Please use " \
          "record.association(:#{@association.reflection.name}).owner instead."
        )
        @association.owner
      end

      def proxy_target
        ActiveSupport::Deprecation.warn(
          "Calling record.#{@association.reflection.name}.proxy_target is deprecated. Please use " \
          "record.association(:#{@association.reflection.name}).target instead."
        )
        @association.target
      end

      def proxy_reflection
        ActiveSupport::Deprecation.warn(
          "Calling record.#{@association.reflection.name}.proxy_reflection is deprecated. Please use " \
          "record.association(:#{@association.reflection.name}).reflection instead."
        )
        @association.reflection
      end
    end
  end
end
Something went wrong with that request. Please try again.