-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Description
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
- add a
belongs_to
relationship that can't be mapped to a model - 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?