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.



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

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



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

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



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

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.



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

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

#clearObject



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

def clear
  delete_all
  self
end

#proxy_associationObject



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

def proxy_association
  @association
end

#proxy_ownerObject



138
139
140
141
142
143
144
145
# File 'lib/active_record/associations/collection_proxy.rb', line 138

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



156
157
158
159
160
161
162
163
# File 'lib/active_record/associations/collection_proxy.rb', line 156

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



147
148
149
150
151
152
153
154
# File 'lib/active_record/associations/collection_proxy.rb', line 147

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



133
134
135
136
# File 'lib/active_record/associations/collection_proxy.rb', line 133

def reload
  proxy_association.reload
  self
end

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

Returns:

  • (Boolean)


78
79
80
81
82
# File 'lib/active_record/associations/collection_proxy.rb', line 78

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



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

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

#to_aryObject Also known as: to_a



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

def to_ary
  load_target.dup
end