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


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

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


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

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
  end

  if 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 'activerecord/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 'activerecord/lib/active_record/associations/collection_proxy.rb', line 108

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

#clearObject


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

def clear
  delete_all
  self
end

#proxy_associationObject


68
69
70
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 68

def proxy_association
  @association
end

#proxy_ownerObject


132
133
134
135
136
137
138
139
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 132

def proxy_owner
  ActiveSupport::Deprecation.warn(
    "Calling record.#{@association.reflection.name}.proxy_owner is deprecated. Please use " \
    "record.association(:#{@association.reflection.name}).owner instead. Or, from an " \
    "association extension you can access proxy_association.owner."
  )
  proxy_association.owner
end

#proxy_reflectionObject


150
151
152
153
154
155
156
157
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 150

def proxy_reflection
  ActiveSupport::Deprecation.warn(
    "Calling record.#{@association.reflection.name}.proxy_reflection is deprecated. Please use " \
    "record.association(:#{@association.reflection.name}).reflection instead. Or, from an " \
    "association extension you can access proxy_association.reflection."
  )
  proxy_association.reflection
end

#proxy_targetObject


141
142
143
144
145
146
147
148
# File 'activerecord/lib/active_record/associations/collection_proxy.rb', line 141

def proxy_target
  ActiveSupport::Deprecation.warn(
    "Calling record.#{@association.reflection.name}.proxy_target is deprecated. Please use " \
    "record.association(:#{@association.reflection.name}).target instead. Or, from an " \
    "association extension you can access proxy_association.target."
  )
  proxy_association.target
end

#reloadObject


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

def reload
  proxy_association.reload
  self
end

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


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

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

#to_aryObject Also known as: to_a


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

def to_ary
  load_target.dup
end