Class: ActiveRecord::Associations::Association

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

Overview

Active Record Associations

This is the root class of all associations (‘+ Foo’ signifies an included module Foo):

Association
  SingularAssociation
    HasOneAssociation + ForeignAssociation
      HasOneThroughAssociation + ThroughAssociation
    BelongsToAssociation
      BelongsToPolymorphicAssociation
  CollectionAssociation
    HasManyAssociation + ForeignAssociation
      HasManyThroughAssociation + ThroughAssociation

Associations in Active Record are middlemen between the object that holds the association, known as the owner, and the associated result set, known as the target. Association metadata is available in reflection, which is an instance of ActiveRecord::Reflection::AssociationReflection.

For example, given

class Blog < ActiveRecord::Base
  has_many :posts
end

blog = Blog.first

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

Direct Known Subclasses

CollectionAssociation, SingularAssociation

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(owner, reflection) ⇒ Association

Returns a new instance of Association.



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/active_record/associations/association.rb', line 41

def initialize(owner, reflection)
  reflection.check_validity!

  @owner, @reflection = owner, reflection
  @disable_joins = @reflection.options[:disable_joins] || false

  reset
  reset_scope

  @skip_strict_loading = nil
end

Instance Attribute Details

#disable_joinsObject (readonly)

Returns the value of attribute disable_joins.



37
38
39
# File 'lib/active_record/associations/association.rb', line 37

def disable_joins
  @disable_joins
end

#ownerObject

:nodoc:



36
37
38
# File 'lib/active_record/associations/association.rb', line 36

def owner
  @owner
end

#reflectionObject (readonly)

Returns the value of attribute reflection.



37
38
39
# File 'lib/active_record/associations/association.rb', line 37

def reflection
  @reflection
end

Instance Method Details

#async_load_targetObject

:nodoc:



198
199
200
201
202
203
# File 'lib/active_record/associations/association.rb', line 198

def async_load_target # :nodoc:
  @target = find_target(async: true) if (@stale_state && stale_target?) || find_target?

  loaded! unless loaded?
  nil
end

#collection?Boolean

Whether the association represent a single record or a collection of records.

Returns:

  • (Boolean)


237
238
239
# File 'lib/active_record/associations/association.rb', line 237

def collection?
  false
end

#create(attributes = nil, &block) ⇒ Object



227
228
229
# File 'lib/active_record/associations/association.rb', line 227

def create(attributes = nil, &block)
  _create_record(attributes, &block)
end

#create!(attributes = nil, &block) ⇒ Object



231
232
233
# File 'lib/active_record/associations/association.rb', line 231

def create!(attributes = nil, &block)
  _create_record(attributes, true, &block)
end

#extensionsObject



169
170
171
172
173
174
175
176
177
# File 'lib/active_record/associations/association.rb', line 169

def extensions
  extensions = klass.default_extensions | reflection.extensions

  if reflection.scope
    extensions |= reflection.scope_for(klass.unscoped, owner).extensions
  end

  extensions
end

#initialize_attributes(record, except_from_scope_attributes = nil) ⇒ Object

:nodoc:



217
218
219
220
221
222
223
224
225
# File 'lib/active_record/associations/association.rb', line 217

def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
  except_from_scope_attributes ||= {}
  skip_assign = [reflection.foreign_key, reflection.type].compact
  assigned_keys = record.changed_attribute_names_to_save
  assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
  attributes = scope_for_create.except!(*(assigned_keys - skip_assign))
  record.send(:_assign_attributes, attributes) if attributes.any?
  set_inverse_instance(record)
end

#inversed_from(record) ⇒ Object



153
154
155
# File 'lib/active_record/associations/association.rb', line 153

def inversed_from(record)
  self.target = record
end

#inversed_from_queries(record) ⇒ Object



157
158
159
160
161
# File 'lib/active_record/associations/association.rb', line 157

def inversed_from_queries(record)
  if inversable?(record)
    self.target = record
  end
end

#klassObject

Returns the class of the target. belongs_to polymorphic overrides this to look at the polymorphic_type field on the owner.



165
166
167
# File 'lib/active_record/associations/association.rb', line 165

def klass
  reflection.klass
end

#load_targetObject

Loads the target if needed and returns it.

This method is abstract in the sense that it relies on find_target, which is expected to be provided by descendants.

If the target is already loaded it is just returned. Thus, you can call load_target unconditionally to get the target.

ActiveRecord::RecordNotFound is rescued within the method, and it is not reraised. The proxy is reset and nil is the return value.



189
190
191
192
193
194
195
196
# File 'lib/active_record/associations/association.rb', line 189

def load_target
  @target = find_target(async: false) if (@stale_state && stale_target?) || find_target?

  loaded! unless loaded?
  target
rescue ActiveRecord::RecordNotFound
  reset
end

#loaded!Object

Asserts the target has been loaded setting the loaded flag to true.



86
87
88
89
# File 'lib/active_record/associations/association.rb', line 86

def loaded!
  @loaded = true
  @stale_state = stale_state
end

#loaded?Boolean

Has the target been already loaded?

Returns:

  • (Boolean)


81
82
83
# File 'lib/active_record/associations/association.rb', line 81

def loaded?
  @loaded
end

#marshal_dumpObject

We can’t dump @reflection and @through_reflection since it contains the scope proc



206
207
208
209
# File 'lib/active_record/associations/association.rb', line 206

def marshal_dump
  ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
  [@reflection.name, ivars]
end

#marshal_load(data) ⇒ Object



211
212
213
214
215
# File 'lib/active_record/associations/association.rb', line 211

def marshal_load(data)
  reflection_name, ivars = data
  ivars.each { |name, val| instance_variable_set(name, val) }
  @reflection = @owner.class._reflect_on_association(reflection_name)
end

#reload(force = false) ⇒ Object

Reloads the target and returns self on success. The QueryCache is cleared if force is true.



72
73
74
75
76
77
78
# File 'lib/active_record/associations/association.rb', line 72

def reload(force = false)
  klass.connection_pool.clear_query_cache if force && klass
  reset
  reset_scope
  load_target
  self unless target.nil?
end

#remove_inverse_instance(record) ⇒ Object

Remove the inverse association, if possible



147
148
149
150
151
# File 'lib/active_record/associations/association.rb', line 147

def remove_inverse_instance(record)
  if inverse = inverse_association_for(record)
    inverse.inversed_from(nil)
  end
end

#resetObject

Resets the loaded flag to false and sets the target to nil.



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

def reset
  @loaded = false
  @stale_state = nil
end

#reset_negative_cacheObject

:nodoc:



66
67
68
# File 'lib/active_record/associations/association.rb', line 66

def reset_negative_cache # :nodoc:
  reset if loaded? && target.nil?
end

#reset_scopeObject



119
120
121
# File 'lib/active_record/associations/association.rb', line 119

def reset_scope
  @association_scope = nil
end

#scopeObject



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/active_record/associations/association.rb', line 107

def scope
  if disable_joins
    DisableJoinsAssociationScope.create.scope(self)
  elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self
    scope.spawn
  elsif scope = klass.global_current_scope
    target_scope.merge!(association_scope).merge!(scope)
  else
    target_scope.merge!(association_scope)
  end
end

#set_inverse_instance(record) ⇒ Object

Set the inverse association, if possible



132
133
134
135
136
137
# File 'lib/active_record/associations/association.rb', line 132

def set_inverse_instance(record)
  if inverse = inverse_association_for(record)
    inverse.inversed_from(owner)
  end
  record
end

#set_inverse_instance_from_queries(record) ⇒ Object



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

def set_inverse_instance_from_queries(record)
  if inverse = inverse_association_for(record)
    inverse.inversed_from_queries(owner)
  end
  record
end

#set_strict_loading(record) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/active_record/associations/association.rb', line 123

def set_strict_loading(record)
  if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
    record.strict_loading!
  else
    record.strict_loading!(false, mode: owner.strict_loading_mode)
  end
end

#stale_target?Boolean

The target is stale if the target no longer points to the record(s) that the relevant foreign_key(s) refers to. If stale, the association accessor method on the owner will reload the target. It’s up to subclasses to implement the stale_state method if relevant.

Note that if the target has not been loaded, it is not considered stale.

Returns:

  • (Boolean)


97
98
99
# File 'lib/active_record/associations/association.rb', line 97

def stale_target?
  loaded? && @stale_state != stale_state
end

#targetObject



53
54
55
56
57
58
# File 'lib/active_record/associations/association.rb', line 53

def target
  if @target.is_a?(Promise)
    @target = @target.value
  end
  @target
end

#target=(target) ⇒ Object

Sets the target of this association to \target, and the loaded flag to true.



102
103
104
105
# File 'lib/active_record/associations/association.rb', line 102

def target=(target)
  @target = target
  loaded!
end