-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
thrown ActiveRecord::AssociationTypeMismatch when assigning a wrong value for a namespaced association #20545
Conversation
@@ -211,7 +211,7 @@ def foreign_key_present? | |||
# the kind of the class of the associated objects. Meant to be used as | |||
# a sanity check when you are about to assign an associated record. | |||
def raise_on_type_mismatch!(record) | |||
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize) | |||
unless record.is_a?(reflection.klass) || (record.is_a?(reflection.class_name.constantize) rescue false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
safe_constantize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, do you mean this?:
record.is_a?(reflection.class_name.safe_constantize)
This breaks the tests because #safe_constantize
returns nil
when couldn't find a match and record.is_a?(nil)
will thrown TypeError: class or module required
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcrec1 you have to split up the one-liner. Using safe_constantize
is preferred to rescue false
as it doesn't hide programming errors:
tpyo rescue false # => false
Something along the lines of:
if klass = reflection.class_name.constantize
record.is_a?(klass)
end
# The class Region (without namespace) shouldn't exist | ||
# in order to the test test_type_mismatch_with_namespaced_class | ||
# be valid | ||
class Admin::Region < ActiveRecord::Base |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to create new global models for a single test-case? We generally try to avoid extending those further. Would be nice if we can reproduce the problem with models defined in the test-case itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test should be done with a class that only exists in a namespace, but maybe a possibility is to undefine Account
and use Admin::Account
. Do you think is better?
Also, I added a new validation to the test to ensure that it is really testing what it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't defining the class inside the test-case fit that description?
class NamespacedBelongsToAssociationsTest < ActiveRecord::TestCase
class Region < ActiveRecord::Base
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, defining Admin::Region
in the test works, I'm updating the code, thanks :)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" | ||
raise ActiveRecord::AssociationTypeMismatch, message | ||
end | ||
end | ||
|
||
def valid_reloaded_class_for?(record) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@senny do you think this is a valid method name? wasn't so sure about how to call this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcrec1 I think it's better to inline that into raise_on_type_mismatch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this?:
unless record.is_a?(reflection.klass) || (record.is_a?(klass) if klass = reflection.class_name.safe_constantize)
Note that this conditional is almost never evaluated, so doing something like this would be less efficient:
valid_reloaded_class = record.is_a?(klass) if klass = reflection.class_name.safe_constantize
unless record.is_a?(reflection.klass) || valid_reloaded_class
Which way should I go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about nesting the conditionals?
unless record.is_a?(reflection.klass)
fresh_class = reflection.class_name.safe_constantize
unless record.is_a?(fresh_class)
raise ActiveRecord::AssociationTypeMismatch
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way works, will change the code.
@@ -23,6 +23,10 @@ def except(adapter_names_to_exclude) | |||
t.string :name | |||
end | |||
|
|||
create_table :admin_region, force: true do |t| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please create the table in the test-case as well.
@senny do you think another change is needed? |
Admin::User.belongs_to(:region) | ||
assert_nil defined?(Region), "This test should be done with a only namespaced class" | ||
assert_raise(ActiveRecord::AssociationTypeMismatch) { Admin::User.new(region: 'wrong value') } | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to define everything inline. The classes for example:
def test_type_mismatch_with_namespaced_class
Admin.const_set "RegionalUser", (Class.new(Admin::User) {
belongs_to(:region)
})
Admin.const_set "Region", Class.new(Admin::User)
e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
Admin::RegionalUser.new(region: 'wrong value')
}
assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message)
ensure
Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
end
Some goes with the schema modifications that you currently do in setup
and teardown
. No need to do that for every test.
@dcrec1 this also needs an entry in the changelog. |
8ed3349
to
7c67d98
Compare
…alue for a namespaced association fixes rails#20541
thrown ActiveRecord::AssociationTypeMismatch when assigning a wrong value for a namespaced association
thrown ActiveRecord::AssociationTypeMismatch when assigning a wrong value for a namespaced association Conflicts: activerecord/CHANGELOG.md
thanks @senny |
fixes #20541