Class: ActiveRecord::Associations::BelongsToAssociation
| Relationships & Source Files | |
| Extension / Inclusion / Inheritance Descendants | |
|
Subclasses:
|
|
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
SingularAssociation,
Association
|
|
|
Instance Chain:
self,
SingularAssociation,
Association
|
|
| Inherits: |
ActiveRecord::Associations::SingularAssociation
|
| Defined in: | activerecord/lib/active_record/associations/belongs_to_association.rb |
Overview
Active Record Belongs To Association
Class Method Summary
-
.new(owner, reflection) ⇒ BelongsToAssociation
constructor
The foreign key and type are physical names for the owner's class, for use with the non-resolving attribute APIs and dirty tracking.
Association - Inherited
Instance Attribute Summary
- #foreign_key readonly
- #foreign_type readonly
- #saved_change_to_target? ⇒ Boolean readonly
- #target_changed? ⇒ Boolean readonly
- #target_previously_changed? ⇒ Boolean readonly
- #updated? ⇒ Boolean readonly
- #find_target? ⇒ Boolean readonly private
- #foreign_key_partially_overlaps_primary_key? ⇒ Boolean readonly private
- #foreign_key_present? ⇒ Boolean readonly private
- #require_counter_update? ⇒ Boolean readonly private
Association - Inherited
| #collection? | Whether the association represents a single record or a collection of records. |
| #disable_joins, | |
| #loaded? | Has the \target been already \loaded? |
| #options, #owner, #reflection, | |
| #stale_target? | The target is stale if the target no longer points to the record(s) that the relevant foreign_key(s) refers to. |
| #target, | |
| #target= | Sets the target of this association to |
| #violates_strict_loading?, #find_target?, | |
| #foreign_key_present? | Returns true if there is a foreign key present on the owner which references the target. |
Instance Method Summary
- #association_route(record = nil)
- #decrement_counters
- #decrement_counters_before_last_save
- #default(&block)
- #handle_dependency
- #increment_counters
- #inversed_from(record)
- #reset
- #clear_reference_with_shared_primary_key(force:) private
- #destroy_association_async_columns(key) private
-
#destroy_association_async_id(route)
private
Prefer the target's persisted values, falling back to the owner for values unavailable after a partial select.
- #invertible_for?(record) ⇒ Boolean private
- #replace(record) private
- #replace_keys(record, force: false) private
- #stale_state private
- #update_counters(by) private
- #update_counters_via_scope(klass, values, by, route) private
SingularAssociation - Inherited
| #build, | |
| #force_reload_reader | Implements the reload reader method, e.g. |
| #reader | Implements the reader method, e.g. |
| #reset | Resets the \loaded flag to |
| #writer | Implements the writer method, e.g. |
| #_create_record, #find_target, #replace, #scope_for_create, #set_new_record | |
Association - Inherited
| #async_load_target, #create, #create!, #extensions, #initialize_attributes, #inversed_from, #inversed_from_queries, | |
| #klass | Returns the class of the target. |
| #load_target | Loads the \target if needed and returns it. |
| #loaded! | Asserts the \target has been loaded setting the \loaded flag to |
| #marshal_dump | We can't dump @reflection and @through_reflection since it contains the scope proc. |
| #marshal_load, | |
| #reload | Reloads the \target and returns |
| #remove_inverse_instance | Remove the inverse association, if possible. |
| #reset | Resets the \loaded flag to |
| #reset_negative_cache, #reset_scope, #scope, | |
| #set_inverse_instance | Set the inverse association, if possible. |
| #set_inverse_instance_from_queries, #set_strict_loading, | |
| #association_scope | The scope for this association. |
| #build_record, #enqueue_destroy_association, | |
| #ensure_klass_exists! | Reader and writer methods call this so that consistent errors are presented when the association target class does not exist. |
| #find_target, | |
| #foreign_key_for? | Returns true if record contains the foreign_key. |
| #inversable?, #inverse_association_for, | |
| #inverse_reflection_for | Can be redefined by subclasses, notably polymorphic belongs_to The record parameter is necessary to support polymorphic inverses as we must check for the association in the specific class of the record. |
| #invertible_for? | Returns true if inverse association on the given record needs to be set. |
| #matches_foreign_key?, | |
| #raise_on_type_mismatch! | Raises |
| #scope_for_create, | |
| #skip_statement_cache? | Returns true if statement cache should be skipped on the association reader. |
| #skip_strict_loading, | |
| #stale_state | This should be implemented to return the values of the relevant key(s) on the owner, so that when stale_state is different from the value stored on the last find_target, the target is stale. |
| #target_scope | Can be overridden (i.e. in |
Constructor Details
.new(owner, reflection) ⇒ BelongsToAssociation
The foreign key and type are physical names for the owner's class, for use with the non-resolving attribute APIs and dirty tracking.
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 13
def initialize(owner, reflection) super @foreign_key = reflection.association_route(origin_class: owner.class)._reference_origin_key @foreign_type = nil if reflection.polymorphic? @foreign_type = owner.resolve_alias(reflection.foreign_type) end end
Instance Attribute Details
#find_target? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 180
def find_target? !loaded? && foreign_key_present? && klass end
#foreign_key (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 9
attr_reader :foreign_key, :foreign_type
#foreign_key_partially_overlaps_primary_key? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 197
def foreign_key_partially_overlaps_primary_key? primary_key = owner.class.primary_key_definition shared_count = foreign_key.count { |key| primary_key.include?(key) } shared_count > 0 && shared_count < foreign_key.size end
#foreign_key_present? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 208
def foreign_key_present? foreign_key.all? { |fk| owner._read_attr(fk) } end
#foreign_type (readonly)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 9
attr_reader :foreign_key, :foreign_type
#require_counter_update? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 184
def require_counter_update? reflection.counter_cache_column && owner.persisted? end
#saved_change_to_target? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 112
def saved_change_to_target? foreign_key.any? { |fk| owner.saved_change_to_attribute?(fk) } end
#target_changed? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 104
def target_changed? foreign_key.any? { |fk| owner.attribute_changed?(fk) } || (!foreign_key_present? && target&.new_record?) end
#target_previously_changed? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 108
def target_previously_changed? foreign_key.any? { |fk| owner.attribute_previously_changed?(fk) } end
#updated? ⇒ Boolean (readonly)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 75
def updated? @updated end
Instance Method Details
#association_route(record = nil)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 22
def association_route(record = nil) reflection.association_route(origin_class: owner.class, destination_class: record ? record.class : klass) end
#decrement_counters
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 79
def decrement_counters update_counters(-1) end
#decrement_counters_before_last_save
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 87
def decrement_counters_before_last_save if reflection.polymorphic? model_type_was = owner.attribute_before_last_save(foreign_type) model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was else model_was = klass end foreign_key_was = foreign_key.map_value { |key| owner.attribute_before_last_save(key) } foreign_key_was = nil if foreign_key.composite? && !foreign_key_was.all? if foreign_key_was && model_was < ActiveRecord::Base route = reflection.association_route(origin_class: owner.class, destination_class: model_was) origin_key_was = route._origin_key.map_value { |key| owner.attribute_before_last_save(key) } update_counters_via_scope(model_was, origin_key_was, -1, route) end end
#default(&block)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 66
def default(&block) writer(owner.instance_exec(&block)) if reader.nil? end
#destroy_association_async_columns(key) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 139
def destroy_association_async_columns(key) declared_aliases = klass.attribute_aliases target_aliases = target.class.attribute_aliases key.map_value do |column| # The job queries the declared class, whose aliases may differ from # the assigned target's. In that case, pass the target's physical column. physical = target_aliases[column] || column (declared_aliases[column] || column) == physical ? column : physical end end
#destroy_association_async_id(route) (private)
Prefer the target's persisted values, falling back to the owner for values unavailable after a partial select.
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 119
def destroy_association_async_id(route) origin_columns = route._origin_key.columns reference_destination_key = route._reference_destination_key route._destination_key.map_value.with_index do |destination_column, index| origin_value = owner._read_attr(origin_columns[index]) if target._has_attribute?(destination_column) destination_value = target.attribute_in_database(destination_column) # An unselected primary key can be present as nil. Use the owner's # reference for that case, but preserve nil query-constraint values. if !destination_value.nil? || !reference_destination_key.include?(destination_column) destination_value else origin_value end else origin_value end end end
#handle_dependency
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 26
def handle_dependency return unless load_target case [:dependent] when :destroy raise ActiveRecord::Rollback unless target.destroy when :destroy_async route = association_route(target) destination_key = route.destination_key id = destroy_association_async_id(route) if !destination_key.composite? && id.is_a?(Array) # The job queries a list of ids, so use tuple syntax to keep this # Array-valued id together as one value. destination_key = ActiveRecord::Key.for(destination_key.columns) id = [id] end association_class = if reflection.polymorphic? owner.public_send(foreign_type) else klass end enqueue_destroy_association( owner_model_name: owner.class.to_s, owner_id: owner.id, association_class: association_class.to_s, association_ids: [id], association_primary_key_column: destroy_association_async_columns(destination_key), ensuring_owner_was_method: .fetch(:ensuring_owner_was, nil) ) else target.public_send([:dependent]) end end
#increment_counters
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 83
def increment_counters update_counters(1) end
#inversed_from(record)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 61
def inversed_from(record) replace_keys(record) super end
#invertible_for?(record) ⇒ Boolean (private)
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 212
def invertible_for?(record) inverse = inverse_reflection_for(record) inverse && (inverse.has_one? || inverse.klass.has_many_inversing) end
#replace(record) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 150
def replace(record) if record raise_on_type_mismatch!(record) set_inverse_instance(record) @updated = true elsif target remove_inverse_instance(target) end replace_keys(record, force: true) self.target = record end
#replace_keys(record, force: false) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 188
def replace_keys(record, force: false) if record.nil? && foreign_key_partially_overlaps_primary_key? clear_reference_with_shared_primary_key(force: force) else route = reflection.association_route(origin_class: owner.class, destination_class: record&.class) route.write(owner, record, force: force) end end
#reset
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 70
def reset super @updated = false end
#stale_state (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 217
def stale_state value = foreign_key.map_value do |fk| owner._read_attr(fk) end foreign_key.composite? ? (value if value.any?) : value end
#update_counters(by) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 164
def update_counters(by) if require_counter_update? && foreign_key_present? if target && !stale_target? target.increment!(reflection.counter_cache_column, by, touch: reflection.[:touch]) else route = association_route update_counters_via_scope(klass, route._origin_key._value_of(owner), by, route) end end end
#update_counters_via_scope(klass, values, by, route) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 175
def update_counters_via_scope(klass, values, by, route) scope = klass.all_queries_scope.where!(route.destination_key.where_hash(values)) scope.update_counters(reflection.counter_cache_column => by, touch: reflection.[:touch]) end