Class: ActiveRecord::Associations::CollectionProxy

Inherits:
Object
  • Object
show all
Defined in:
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 '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
103
104
# File '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 |record|
      proxy_association.send :set_owner_attributes, record
      proxy_association.send :add_to_target, record
      yield(record) if block_given?
    end.tap do |record|
      proxy_association.send :set_inverse_instance, record
    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


117
118
119
# File 'lib/active_record/associations/collection_proxy.rb', line 117

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.


108
109
110
# File 'lib/active_record/associations/collection_proxy.rb', line 108

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

#clearObject


122
123
124
125
# File 'lib/active_record/associations/collection_proxy.rb', line 122

def clear
  delete_all
  self
end

#proxy_associationObject


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

def proxy_association
  @association
end

#reloadObject


127
128
129
130
# File 'lib/active_record/associations/collection_proxy.rb', line 127

def reload
  proxy_association.reload
  self
end

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

Returns:

  • (Boolean)

71
72
73
74
75
# File '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 '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


112
113
114
# File 'lib/active_record/associations/collection_proxy.rb', line 112

def to_ary
  load_target.dup
end