Shoulda-matchers: ensure_inclusion_of does not work with polymorphic attributes

Created on 26 Aug 2014  Â·  12Comments  Â·  Source: thoughtbot/shoulda-matchers

So I had ensure_inclusion_of working just fine, then I migrated some things to use FactoryGirl instead of explicit create statements and suddenly it's failing. If I dup the object created by FactoryGirl, it works again. Here's an example in my spec:

  before { @job_spec = FactoryGirl.create(:job_spec) }
  subject { @job_spec }

  it { should ensure_inclusion_of(:job_template_type).in_array(JobSpec::JOB_TEMPLATE_TYPES) }

  context "using a copy to get around ensure_inclusion_of bug" do
    before { @job_spec_dup = @job_spec.dup }
    subject { @job_spec_dup }

    it { should ensure_inclusion_of(:job_template_type).in_array(JobSpec::JOB_TEMPLATE_TYPES) }
  end

The first test fails, the second is successful:

JobSpec
  should ensure inclusion of job_template_type in ["TplBirstSoapGenericCommand", "TplBirstDuplicateSpace"] (FAILED - 1)
  using a copy to get around ensure_inclusion_of bug
    should ensure inclusion of job_template_type in ["TplBirstSoapGenericCommand", "TplBirstDuplicateSpace"]

Failures:

  1) JobSpec should ensure inclusion of job_template_type in ["TplBirstSoapGenericCommand", "TplBirstDuplicateSpace"]
     Failure/Error: it { should ensure_inclusion_of(:job_template_type).in_array(JobSpec::JOB_TEMPLATE_TYPES) }
     NameError:
       wrong constant name shouldamatchersteststring
     # ./spec/models/job_spec_spec.rb:19:in `block (2 levels) in <top (required)>'

Finished in 0.3576 seconds (files took 1.73 seconds to load)
15 examples, 1 failure

Failed examples:

rspec ./spec/models/job_spec_spec.rb:19 # JobSpec should ensure inclusion of job_template_type in ["TplBirstSoapGenericCommand", "TplBirstDuplicateSpace"]
Bug PR Welcome

Most helpful comment

Still present in 2019. Will see if I can help.

All 12 comments

Okay, can you re-run these tests with the --backtrace option?

Failures:

  1) JobSpec should ensure inclusion of job_template_type in ["TplBirstSoapGenericCommand", "TplBirstDuplicateSpace"]
     Failure/Error: it { should ensure_inclusion_of(:job_template_type).in_array(JobSpec::JOB_TEMPLATE_TYPES) }
     NameError:
       wrong constant name shouldamatchersteststring
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/inflector/methods.rb:238:in `const_get'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/inflector/methods.rb:238:in `block in constantize'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/inflector/methods.rb:236:in `each'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/inflector/methods.rb:236:in `inject'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/inflector/methods.rb:236:in `constantize'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/core_ext/string/inflections.rb:66:in `constantize'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/associations/belongs_to_polymorphic_association.rb:7:in `klass'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/associations/belongs_to_association.rb:37:in `find_target?'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/associations/association.rb:138:in `load_target'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/associations/association.rb:53:in `reload'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/associations/singular_association.rb:9:in `reader'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/autosave_association.rb:287:in `validate_single_association'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/autosave_association.rb:209:in `block in add_autosave_association_callbacks'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/autosave_association.rb:157:in `instance_eval'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/autosave_association.rb:157:in `block in define_non_cyclic_method'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:424:in `block in make_lambda'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:184:in `call'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:184:in `block in simple'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `call'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `block in simple'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `call'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `block in simple'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `call'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:185:in `block in simple'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `call'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `run_callbacks'
     # ./vendor/gems/activemodel-4.1.4/lib/active_model/validations.rb:376:in `run_validations!'
     # ./vendor/gems/activemodel-4.1.4/lib/active_model/validations/callbacks.rb:111:in `block in run_validations!'
     # ./vendor/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:82:in `run_callbacks'
     # ./vendor/gems/activemodel-4.1.4/lib/active_model/validations/callbacks.rb:111:in `run_validations!'
     # ./vendor/gems/activemodel-4.1.4/lib/active_model/validations.rb:317:in `valid?'
     # ./vendor/gems/activerecord-4.1.4/lib/active_record/validations.rb:70:in `valid?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/validation_message_finder.rb:61:in `validate_instance'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/validation_message_finder.rb:57:in `validated_instance'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/validation_message_finder.rb:53:in `errors'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/validation_message_finder.rb:23:in `has_messages?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:256:in `has_messages?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:252:in `errors_match?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:222:in `block in matches?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:219:in `each
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:219:in `each'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:219:in `none?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/allow_value_matcher.rb:219:in `matches?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/validation_matcher.rb:43:in `allows_value_of'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb:388:in `disallows_value_outside_of_array?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb:326:in `matches_for_array?'
     # ./vendor/gems/shoulda-matchers-2.6.2/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb:304:in `matches?'
     # ./vendor/gems/rspec-expectations-3.0.4/lib/rspec/expectations/handler.rb:48:in `handle_matcher'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/memoized_helpers.rb:81:in `should'
     # ./spec/models/job_spec_spec.rb:20:in `block (2 levels) in <top (required)>'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:148:in `instance_exec'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:148:in `block in run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:210:in `call'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:210:in `block (2 levels) in <class:Procsy>'
     # ./vendor/gems/rspec-rails-3.0.2/lib/rspec/rails/adapters.rb:72:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:294:in `instance_exec'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:294:in `instance_exec'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/hooks.rb:430:in `block (2 levels) in run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:210:in `call'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:210:in `block (2 levels) in <class:Procsy>'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/hooks.rb:432:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/hooks.rb:485:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:303:in `with_around_example_hooks'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example.rb:145:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example_group.rb:494:in `block in run_examples'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example_group.rb:490:in `map'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example_group.rb:490:in `run_examples'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/example_group.rb:457:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:112:in `block (2 levels) in run_specs'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:112:in `map'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:112:in `block in run_specs'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/reporter.rb:54:in `report'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:108:in `run_specs'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:86:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:70:in `run'
     # ./vendor/gems/rspec-core-3.0.4/lib/rspec/core/runner.rb:38:in `invoke'
     # ./vendor/gems/rspec-core-3.0.4/exe/rspec:4:in `<top (required)>'
     # ./vendor/bin/rspec:23:in `load'
     # ./vendor/bin/rspec:23:in `<main>'

Ah. So it looks like it's failing because the attribute in question is polymorphic and thus ActiveRecord expects any value for this attribute to be a known class. I'll mark this as a bug, but in the meantime you can use allow_value to get around this:

it { should allow_value(JobSpec::JOB_TEMPLATE_TYPES).for(:job_template_type) }

it do
  all_other_models = ActiveRecord::Base.subclasses.map(&:to_s) - JobSpec::JOB_TEMPLATE_TYPES
  should_not allow_value(*all_other_models).for(:job_template_type)
end

Using Ruby 2.1.5, Rails 4.2.0, and Shoulda-Matchers 2.8.0, shoulda-matchers also fails trying to validate_inclusion_of [:user] for resource_type in the popular 'rolify' gem. The Role class:

class Role < ActiveRecord::Base
has_and_belongs_to_many :users, join_table: :users_roles
belongs_to :resource, polymorphic: true
validates :resource_type,
inclusion: { in: [:user] },
allow_nil: true
scopify
end

My attempted validation of validates_inclusion_of(:resource_type):
it { should validate_inclusion_of(:resource_type).in_array([nil, "user"]) }

The error message I get (it doesn't matter whether or not I include nil in the array):
Failures:
1) Role should ensure inclusion of resource_type in [nil, "user"]
Failure/Error: it { should validate_inclusion_of(:resource_type).in_array([nil, "user"]) }
[nil, "user"] doesn't match array in validation

(If I use only ["user"], the error I get is the expected ["user"] doesn't match array in validation)

I added some puts in the shoulda-matcher code to try to figure out what was going on:
allows_all_values_in_array @array = [nil, "user"]
allows_all_values_in_array: @low_message = nil
allows_all_values_in_array: true, ,
allows_all_values_in_array: @low_message = nil
allows_all_values_in_array: false, user,
matches_for_array: false true true true

(If I use only ["user"], the output is:
allows_all_values_in_array @array = ["user"]
allows_all_values_in_array: @low_message = nil
allows_all_values_in_array: false, user,
matches_for_array: false true true true)

My next attempted validation using allows_value doesn't work either:
Failures:
1) Role should allow resource_type to be set to "user"
Failure/Error: it { should allow_value("user").for(:resource_type) }
Did not expect errors when resource_type is set to "user",
got errors:
* "is not included in the list" (attribute: resource_type, value: "user")

(Note that "Role should allow resource_type to be set to nil" does validate correctly, as would be expected from the output above.)

@cindyward1 Doesn't resource_type take the name of a class as a value, and not a symbol? What if you say:

validates :resource_type, inclusion: { in: ['User'] }, allow_nil: true

and then to test it:

should validate_inclusion_of(:resource_type).in_array('User').allow_nil

What you sent me works fine and explains why resource_type is always NULL in my development database. I misunderstood how to modify the Role class. Thanks for finding my bug ... Cindy

 On Tuesday, February 17, 2015 9:18 AM, Elliot Winkler <[email protected]> wrote:

@cindyward1 Doesn't resource_type take the name of a class as a value, and not a symbol? What if you say:validates :resource_type, inclusion: { in: ['User'] }, allow_nil: trueand then to test it:should validate_inclusion_of(:resource_type).in_array('User').allow_nil
—
Reply to this email directly or view it on GitHub.

Try this , It's works for me
it do
should validate_inclusion_of(:resource_type)
.in_array(Rolify.resource_types)
.allow_nil(true)
end

@mcmire I think I'm hitting this issue as well.
I'm using ruby v2.4.1, rails v5.1.4 and shoulda-matchers v3.1.2

class Favourite < ApplicationRecord
  belongs_to :user
  belongs_to :favourable, polymorphic: true

  validates_presence_of :user_id, :favourable_id, :favourable_type
  validates :favourable_type, inclusion: { in: %w[Project],
                                           message: "%{value} can't be favourited" }

Rspec tests:

  context 'validation' do
    let!(:user) { create(:user) }
    let!(:project) { create(:project) }
    let!(:favourite_project) { user.favourites.build(favourable: project) }

    context 'favourable type' do
      specify 'valid type' do
        expect(favourite_project).to validate_inclusion_of(:favourable_type)
          .in_array(%w[Project])
      end
    end
  end

Error thrown:

     NameError:
       wrong constant name shoulda-matchers test string

@amnesia7 Cool, thanks for reporting this :) This is definitely something we need help fixing.

Let me know if you need any more info or need anything testing, or if there's anything straight forward that I can help with to get this resolved.

Col


On Thu, 26/10/17, Elliot Winkler notifications@github.com wrote:

Subject: Re: [thoughtbot/shoulda-matchers] ensure_inclusion_of does not work with polymorphic attributes (#574)
To: "thoughtbot/shoulda-matchers" shoulda-matchers@noreply.github.com
Cc: "amnesia7" col.gibson@gmail.com, "Mention" mention@noreply.github.com
Date: Thursday, 26 October, 2017, 21:12

@amnesia7 Cool, thanks
for reporting this :) This is definitely something we need
help fixing.

—
You are receiving this because you were mentioned.
Reply to this email directly, view
it on GitHub, or mute
the thread.

Still present in 2019. Will see if I can help.

Hey folks. In an effort to lighten our load as maintainers and be able to serve you better in the future, the shoulda-matchers team is working on cleaning out the cobwebs in this repo by pruning the backlog. As there are few of us, there are a lot of items that will simply never earn our attention in a reasonable time frame, and rather than giving you an empty promise, we think it makes more sense to focus on more recent issues. That means, unfortunately, that we must close this issue.

Don't take this the wrong way: our aim is not to diminish the effort people have made or dismiss problems that have been raised. If you feel that we should reopen this issue, then please let us know so that we can reprioritize it. Thanks!

Was this page helpful?
0 / 5 - 0 ratings