Skip to content
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

Merged
merged 1 commit into from
Jun 23, 2015

Conversation

dcrec1
Copy link
Contributor

@dcrec1 dcrec1 commented Jun 13, 2015

fixes #20541

@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safe_constantize

Copy link
Contributor Author

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.

Copy link
Member

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

@dcrec1
Copy link
Contributor Author

dcrec1 commented Jun 15, 2015

@rafaelfranca

# 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
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor Author

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)
Copy link
Contributor Author

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.

Copy link
Member

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!

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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|
Copy link
Member

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.

@dcrec1
Copy link
Contributor Author

dcrec1 commented Jun 20, 2015

@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
Copy link
Member

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.

@senny
Copy link
Member

senny commented Jun 22, 2015

@dcrec1 this also needs an entry in the changelog.

@dcrec1 dcrec1 force-pushed the 20541 branch 2 times, most recently from 8ed3349 to 7c67d98 Compare June 22, 2015 15:32
@senny
Copy link
Member

senny commented Jun 23, 2015

I merged a slightly modified version of this PR. The changes are contained in the merge commit (8e27fd9).

@dcrec1 thank you 💛

@senny senny merged commit 828d0d7 into rails:master Jun 23, 2015
senny added a commit that referenced this pull request Jun 23, 2015
thrown ActiveRecord::AssociationTypeMismatch when assigning a wrong value for a namespaced association
senny added a commit that referenced this pull request Jun 23, 2015
thrown ActiveRecord::AssociationTypeMismatch when assigning a wrong value for a namespaced association

Conflicts:
	activerecord/CHANGELOG.md
@dcrec1
Copy link
Contributor Author

dcrec1 commented Jun 23, 2015

thanks @senny

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants