Class: ActiveRecord::Associations::CollectionProxy

Inherits:
Object
  • Object
show all
Defined in:
activerecord/lib/active_record/associations/collection_proxy.rb

Overview

Association proxies in Active Record are middlemen between the object that holds the association, known as the @owner, and the actual associated object, known as the @target. The kind of association any proxy is about is available in @reflection. That's an instance of the class ActiveRecord::Reflection::AssociationReflection.

For example, given

class Blog < ActiveRecord::Base
  has_many :posts
end

blog = Blog.first

the association proxy in blog.posts has the object in blog as @owner, the collection of its posts as @target, and the @reflection object represents a :has_many macro.

This class has most of the basic instance methods removed, and delegates unknown methods to @target via method_missing. As a corner case, it even removes the class method and that's why you get

blog.posts.class # => Array

though the object behind blog.posts is not an Array, but an ActiveRecord::Associations::HasManyAssociation.

The @target 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.

Instance Method Summary collapse

Constructor Details

#initialize(association) ⇒ CollectionProxy

Returns a new instance of CollectionProxy


53
54
55
56
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 53

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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object


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
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 77

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

  elsif target.respond_to?(method) || (!proxy_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

Instance Method Details

#<<(*records) ⇒ Object Also known as: push


115
116
117
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 115

def <<(*records)
  proxy_association.concat(records) && self
end

#===(other) ⇒ Object

Forwards === explicitly to the target because the instance method removal above doesn't catch it. Loads the target if needed.


106
107
108
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 106

def ===(other)
  other === load_target
end

#clearObject


120
121
122
123
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 120

def clear
  delete_all
  self
end

#proxy_associationObject


60
61
62
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 60

def proxy_association
  @association
end

#reloadObject


125
126
127
128
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 125

def reload
  proxy_association.reload
  self
end

#respond_to?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)

71
72
73
74
75
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 71

def respond_to?(name, include_private = false)
  super ||
  (load_target && target.respond_to?(name, include_private)) ||
  proxy_association.klass.respond_to?(name, include_private)
end

#scopedObject


64
65
66
67
68
69
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 64

def scoped
  association = @association
  association.scoped.extending do
    define_method(:proxy_association) { association }
  end
end

#to_aryObject Also known as: to_a


110
111
112
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 110

def to_ary
  load_target.dup
end