Skip to content

belongs_to relationship silently returns nil when an invalid foreign key is provided #39214

@beirigo

Description

@beirigo

This report is related to this post on the "A May of WTFs" forum topic.

The main point that I raised is that belongs_to and has_one behave differently when set up incorrectly. While has_one raises a NameError, belongs_to simply returns nil.

Steps to reproduce

  1. add a belongs_to relationship that can't be mapped to a model
  2. add a has_one relationship that can't be mapped to a model
# Neither Foo or Bar are defined
class Post < ActiveRecord::Base
  belongs_to :foo
  has_one :bar
end

bug report gist: https://gist.github.com/beirigo/24c5e09a735aaf69942ae832448c712a

Expected behavior

post = Post.create!

post.foo
#=> NameError: uninitialized constant Foo

post.bar
#=> NameError: uninitialized constant Bar

Actual behavior

post = Post.create!

post.foo
#=> nil

post.bar
#=> NameError: uninitialized constant Bar

System configuration

Rails version:
master, 6.0.0, 5.0.0
I couldn't test versions older than 5 because trying to setup the native dependencies became a nightmare :(
Ruby version:
2.7.0

My findings

I narrowed this down to the respective implementation of find_target? on each association.
While both implementations check that the klass is defined, that check is at the very end of a boolean expression and therefore has low precedence:

# BelongsToAssociation
#  foreign_key_present? is false so klass is never invoked 
def find_target?
  !loaded? && foreign_key_present? && klass
end

# HasOneAssociation (deferred to ActiveRecord::Associations)
def find_target?
  !loaded? && (!owner.new_record? || foreign_key_present?) && klass
end

I believe that at some point, has_one's implementation of foreign_key_present?
tries to load the target class through reflection, somehow raising a NameError.

Would moving the klass check to the beginning of those boolean expressions be a viable solution? what would the implications of such change be?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions