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

validate_numericality_of matcher trying to set a string value in 3.0 #784

Closed
dillonwelch opened this issue Oct 2, 2015 · 35 comments
Closed
Milestone

Comments

@dillonwelch
Copy link

I'm trying to upgrade to version 3.0 and am running into this issue in one of my model specs. The validate_numericality_of matcher is trying to set a float field to a string, and erroring it out when that doesn't work. I would think that it would be expected for that assignment to fail. 😕

# spec/rails_helper.rb
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# migration file
t.float :hours_worked

# model file
validates :hours_worked, numericality: true

# spec file
it { expect(subject).to validate_numericality_of(:hours_worked) }

# error
Failure/Error: it { expect(subject).to validate_numericality_of(:hours_worked) }
Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
  Expected Class to be able to set hours_worked to "abcd", but got 0.0 instead.
@chris-obv
Copy link

We're receiving the same error.

@brunoocasali
Copy link

👍

@mcmire
Copy link
Collaborator

mcmire commented Oct 2, 2015

This was a conscious decision to ensure that people are writing tests that are true and not misleading.

validate_numericality_of makes the assumption that if your attribute is set to a non-numeric value, then it will cause your model to be invalid. But this isn't actually happening: since your column is numeric itself, it will typecast any value to a numeric (in this case a float). So your validation won't ever fail, because all values are now always numeric.

The exception you're getting is low-level and obviously we should make some improvements to the message, but basically, it's indicating that since the validation always passes, you can remove it and the test itself. Does this make sense?

@dillonwelch
Copy link
Author

Yes, that makes sense now that you've explained it. Thanks! Definitely agree on seeing an improved message 👍

@sunderwood
Copy link

I use validate_numericality_of to validate that the number is within a certain range of values (given that I know the field is numeric). Using validate_numericality_of provides better default error messages and options than validate_inclusion_of.

I can understand the desire to ensure that people are not adding superfluous validations to the model. Your test, and error are fine if someone does not include any of the options for the validates_numericality_of. Perhaps the test should warn that the field is already numeric instead of failing the test.

In the example above, if the validation was

validates :hours_worked, numericality: {greater_than: 0}

I would expect

it { expect(subject).to validate_numericality_of(:hours_worked).is_greater_than(0)}

to pass, but it doesn't.

@cofiem
Copy link

cofiem commented Oct 3, 2015

I understand and appreciate the rationale behind setting a numerical field to a string as a test. Similar to what @sunderwood says, I can not work out how to get Rails' built-in numericality validation to actually pass the validate_numericality_of test.

For example, I would expect this test to pass, but it does not:

# validation
validates :channels, presence: true, numericality: {only_integer: true, greater_than: 0}

# tests
it { is_expected.to validate_presence_of(:channels) }
it { is_expected.to validate_numericality_of(:channels).is_greater_than(0).only_integer }

# error
validation should only allow integers for channels which are greater than 0
     Failure/Error: it { is_expected.to validate_numericality_of(:channels).is_greater_than(0).only_integer }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       Expected Class to be able to set channels to "abcd", but got 0 instead.

Can you clarify how this is intended to work? Should the validate_numericality_of pass using Rails built-in numericality validation?

@celsoMartins
Copy link

I'm getting this error too.

And so, if I understood well, I'll need to get rid of shoulda and I need to use pure rspec to test two words (greather_than) on my model.

My new specs:

    context 'with zero values' do
      it 'refuses below zero values' do
        workforce.amount_centavos = 0
        expect(workforce).not_to be_valid
      end
    end

    context 'with negative values' do
      it 'refuses below zero values' do
        workforce.amount_centavos = -1
        expect(workforce).not_to be_valid
      end
    end

instead of

it { expect(workforce).to validate_numericality_of(:amount_centavos).is_greater_than(0) }

Am I right? If it is correct, I choose to keep the 2.8 version until we have an option or shoulda reconsider it.

@mcmire
Copy link
Collaborator

mcmire commented Oct 3, 2015

Yes, the bug where the numericality matcher raises an error when using comparison qualifiers (greater_than, less_than, etc.) is a known issue (raised in another issue recently) and I'll put out a fix for that soon. I'd stay at 2.8.0 until that's fixed.

@celsoMartins
Copy link

thanks.

@QuotableWater7
Copy link

👍

@arronmabrey
Copy link

I ran into this issue as well, but before looking for it in github issues I made a small focused reproduction script. I figure I will add it here if it helps anyone.

#!/usr/bin/env ruby

begin
  require 'bundler/inline'
rescue LoadError => e
  $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
  raise e
end

gemfile(true) do
  source 'https://rubygems.org'

  gem 'sqlite3'
  gem 'activerecord', '4.2.4'
  gem 'rspec', '3.3.0'
  gem 'shoulda-matchers', '3.0.0'
end

require 'active_record'
require 'rspec/autorun'
require 'logger'

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :active_record
    with.library :active_model
  end
end

ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts do |t|
    t.integer :comment_count
  end
end

class Post < ActiveRecord::Base
  validates_numericality_of :comment_count, only_integer: true, greater_than_or_equal_to: 0, allow_nil: true
end

RSpec.describe Post, type: :model do
  it { should validate_numericality_of(:comment_count).only_integer.is_greater_than_or_equal_to(0).allow_nil }
end

Running the above script fails with:

Failures:

  1) Post should only allow integers for comment_count which are greater than or equal to 0
     Failure/Error: it { should validate_numericality_of(:comment_count).only_integer.is_greater_than_or_equal_to(0).allow_nil }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       Expected Class to be able to set comment_count to "abcd", but got 0 instead.
     # ./shoulda-matchers-fail.rb:55:in `block (2 levels) in <main>'

Finished in 0.00613 seconds (files took 0.44861 seconds to load)
1 example, 1 failure

Failed examples:

rspec  # Post should only allow integers for comment_count which are greater than or equal to 0

@mcmire
Copy link
Collaborator

mcmire commented Oct 7, 2015

Yup, I'm working on this 😃

@arronmabrey
Copy link

@mcmire thanks so much 🎉 🎈

@mcmire
Copy link
Collaborator

mcmire commented Oct 9, 2015

This is fixed in 18b2859.

@zerocool4u2
Copy link

Hi, really appreciate your work, I'm having a couple of troubles like this, specifically with decimals attributes an integers, with decimals I'm getting "abcd" type casted to BigDecimals(0.0) in any type of range limit and with the integer typecast floats to integers(0.1 to 0) when check only_integer, i get the message for submitting an issue, but it's looks like that is the same problem of typecast(the commit doesn't fix my cases), so I'm guessing that the current solution its patch up cases, should i still submit the issues? regards! and again, really appreciated man, take care

@mcmire
Copy link
Collaborator

mcmire commented Oct 9, 2015

@zerocool4u2 Sorry about that. What you're seeing may be fixed by the commit I just made. Can you try pointing to master temporarily and seeing if that resolves the issues?

@zerocool4u2
Copy link

@mcmire nope, there are still there, here are the failures I'm getting

Bundle
Using shoulda-matchers 3.0.0 from git:/thoughtbot/shoulda-matchers.git (at master)

Decimal

#migration
t.decimal :lat

#model
validates :lat, presence: true, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }

#spec
it { expect(address).to validate_numericality_of(:lat).is_greater_than_or_equal_to(-90).is_less_than_or_equal_to(90) }

#error
  1) Address ActiveModel Validations should only allow numbers for lat which are greater than or equal to -90 and less than or equal to 90
     Failure/Error: it { expect(address).to validate_numericality_of(:lat).is_greater_than_or_equal_to(-90).is_less_than_or_equal_to(90) }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       The allow_value matcher attempted to set :lat on Address to "abcd", but
       when the attribute was read back, it had stored
       #<BigDecimal:67ea2d8,'0.0',9(9)> instead.

       This creates a problem because it means that the model is behaving in a
       way that is interfering with the test -- there's a mismatch between the
       test that was written and test that was actually run.

       There are a couple of reasons why this could be happening:

       * The writer method for :lat has been overridden and contains custom
         logic to prevent certain values from being set or change which values
         are stored.
       * ActiveRecord is typecasting the incoming value.

       Regardless, the fact you're seeing this message usually indicates a
       larger problem. Please file an issue on the GitHub repo for
       shoulda-matchers, including details about your model and the test
       you've written, and we can point you in the right direction:

       https:/thoughtbot/shoulda-matchers/issues
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:462:in `ensure_that_attribute_was_set!'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:454:in `set_attribute'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:448:in `value_matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `block in matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `each'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `all?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/disallow_value_matcher.rb:16:in `matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:482:in `block in first_failing_submatcher'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `each'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `detect'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `first_failing_submatcher'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:408:in `matches?'
     # ./spec/models/address_spec.rb:43:in `block (3 levels) in <top (required)>'

Integer

#migration
t.integer :number

#model
validates :number, presence: true, numericality: { only_integer: true }

#spec
it { expect(region).to validate_numericality_of(:number).only_integer }

#error
  1) Region ActiveModel Validations should only allow integers for number
     Failure/Error: it { expect(region).to validate_numericality_of(:number).only_integer }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       The allow_value matcher attempted to set :number on Region to 0.1, but
       when the attribute was read back, it had stored 0 instead.

       This creates a problem because it means that the model is behaving in a
       way that is interfering with the test -- there's a mismatch between the
       test that was written and test that was actually run.

       There are a couple of reasons why this could be happening:

       * The writer method for :number has been overridden and contains custom
         logic to prevent certain values from being set or change which values
         are stored.
       * ActiveRecord is typecasting the incoming value.

       Regardless, the fact you're seeing this message usually indicates a
       larger problem. Please file an issue on the GitHub repo for
       shoulda-matchers, including details about your model and the test
       you've written, and we can point you in the right direction:

       https:/thoughtbot/shoulda-matchers/issues
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:462:in `ensure_that_attribute_was_set!'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:454:in `set_attribute'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:448:in `value_matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `block in matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `each'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `all?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/disallow_value_matcher.rb:16:in `matches?'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:482:in `block in first_failing_submatcher'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `each'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `detect'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `first_failing_submatcher'
     # /home/zerocool4u2/.rvm/gems/ruby-2.2.0/bundler/gems/shoulda-matchers-369e9d4c36cf/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:408:in `matches?'
     # ./spec/models/region_spec.rb:32:in `block (3 levels) in <top (required)>'

I hope you find them useful, regards

@zerocool4u2
Copy link

Tracking the file
shoulda-matchers/lib/shoulda/matchers/active_model/allow_value_matcher.rb
i find out that until the commit 73cf275 all my test works, they break at 9d9dc4e, regards

@wellsonjain
Copy link

@zerocool4u2 I have the same problem as well. thank you for reporting this issue, I got stuck on this problem for a few days.
@mcmire I've set the version to be the specific commit 18b2859 and I still got error, Here is my model, rspec and error msg.

# model
validates :price, numericality: { greater_than_or_equal_to: 0 }, presence: true

# rspec
it { should validate_numericality_of(:price).is_greater_than_or_equal_to(0) }

#error 
Failure/Error: it { should validate_numericality_of(:price).is_greater_than_or_equal_to(0) }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       The allow_value matcher attempted to set :price on Product to "abcd",
       but when the attribute was read back, it had stored
       #<BigDecimal:7fca4f901c78,'0.0',9(9)> instead.

       This creates a problem because it means that the model is behaving in a
       way that is interfering with the test -- there's a mismatch between the
       test that was written and test that was actually run.

       There are a couple of reasons why this could be happening:

       * The writer method for :price has been overridden and contains custom
         logic to prevent certain values from being set or change which values
         are stored.
       * ActiveRecord is typecasting the incoming value.

       Regardless, the fact you're seeing this message usually indicates a
       larger problem. Please file an issue on the GitHub repo for
       shoulda-matchers, including details about your model and the test
       you've written, and we can point you in the right direction:

       https:/thoughtbot/shoulda-matchers/issues
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:462:in `ensure_that_attribute_was_set!'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:454:in `set_attribute'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:448:in `value_matches?'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `block in matches?'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `each'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `all?'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/allow_value_matcher.rb:406:in `matches?'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/disallow_value_matcher.rb:16:in `matches?'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:482:in `block in first_failing_submatcher'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `each'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `detect'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:481:in `first_failing_submatcher'
     # /Users/wellsonjain/.rvm/gems/ruby-2.2.3@market-api/bundler/gems/shoulda-matchers-18b2859d2522/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:408:in `matches?'
     # ./spec/models/product_spec.rb:14:in `block (2 levels) in <top (required)>'

@mcmire
Copy link
Collaborator

mcmire commented Oct 10, 2015

@zerocool4u2

In the case of the decimal, that's a bug -- I'm taking floats and integers into account but I forgot decimal.

In the case of the integer column, you don't actually need the validation or test at all -- an integer column already typecasts any incoming value to an integer, so there's no need for the validation, as it will always pass.

@mcmire
Copy link
Collaborator

mcmire commented Oct 10, 2015

@wellsonjain Same problem as @zerocool4u2 -- I'm guessing that that column is a decimal, and I forgot about that one. I'll work on that next.

@graygilmore
Copy link

I'm getting a similar error:

product.rb

class Product < ActiveRecord::Base
  validates :price, numericality: {
    greater_than_or_equal_to: 0,
    only_integer: true
  }
end

product_spec.rb

require "rails_helper"

RSpec.describe Product, type: :model do
  it do
    should validate_numericality_of(:price).
      is_greater_than_or_equal_to(0).
      only_integer
  end
end

Failure

Failures:

  1) Product should only allow integers for price which are greater than or equal to 0
     Failure/Error: should validate_numericality_of(:price).
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       The allow_value matcher attempted to set :price on Product to 0.1, but
       when the attribute was read back, it had stored 0 instead.

       This creates a problem because it means that the model is behaving in a
       way that is interfering with the test -- there's a mismatch between the
       test that was written and test that was actually run.

       There are a couple of reasons why this could be happening:

       * The writer method for :price has been overridden and contains custom
         logic to prevent certain values from being set or change which values
         are stored.
       * ActiveRecord is typecasting the incoming value.

       Regardless, the fact you're seeing this message usually indicates a
       larger problem. Please file an issue on the GitHub repo for
       shoulda-matchers, including details about your model and the test
       you've written, and we can point you in the right direction:

       https:/thoughtbot/shoulda-matchers/issues
     # ./spec/models/product_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.04772 seconds (files took 1.29 seconds to load)
5 examples, 1 failure

Failed examples:

rspec ./spec/models/product_spec.rb:6 # Product should only allow integers for price which are greater than or equal to 0

Looks like it's trying to set the value to 0.1 which is not an integer.

@joshteng
Copy link

I'm getting an unexpected error message.

I have in my model

validates :value, numericality: { only_integer: true, greater_than_or_equal_to: 0 }

and this as my spec

it { should validate_numericality_of(:value).only_integer }

I would expect this to pass but I'm getting an error message:

     Failure/Error: it { should validate_numericality_of(:value).only_integer }
     Shoulda::Matchers::ActiveModel::AllowValueMatcher::CouldNotSetAttributeError:
       The allow_value matcher attempted to set :value on KarmaPoint to 0.1,
       but when the attribute was read back, it had stored 0 instead.

       This creates a problem because it means that the model is behaving in a
       way that is interfering with the test -- there's a mismatch between the
       test that was written and test that was actually run.

       There are a couple of reasons why this could be happening:

       * The writer method for :value has been overridden and contains custom
         logic to prevent certain values from being set or change which values
         are stored.
       * ActiveRecord is typecasting the incoming value.

       Regardless, the fact you're seeing this message usually indicates a
       larger problem. Please file an issue on the GitHub repo for
       shoulda-matchers, including details about your model and the test
       you've written, and we can point you in the right direction:

       https:/thoughtbot/shoulda-matchers/issues

It says it tried to set the value to 0.1 but it had stored 0. That's correct because I'm restricting to integer. Why is that a false validation? Just checking to see if I'm missing something.

I'm using shoulda-matchers 3.0.1.

@betesh
Copy link
Contributor

betesh commented Oct 26, 2015

Because when you set it to 0.1, ActiveRecord will automatically cast it as an integer, i.e. 0.1.to_i, which is 0, so it's impossible to set an invalid value.

You do need to test the greater_than_or_equal_to part and you can use validate_numericality_of(:value) for that. Only the only_integer is unnecessary, so you can remove it from both your model and your spec.

@joshteng
Copy link

Thx @betesh

It makes sense, but wouldn't that be making assumptions about my database's data type? I know it probably doesn't make sense to hv integer validation and have the database data type to be a float/decimal. What if I wanted to assert integer validation in situations like these?

@betesh
Copy link
Contributor

betesh commented Oct 26, 2015

shoulda-matchers detects the data type. If you change your database column to a float, this test will start passing. But you're better off relying on built-in ActiveRecord features to keep invalid data out of your database than writing additional validations.

@mcmire's comment below explains this better. It's not shoulda-matchers that's detecting the type. It's ActiveRecord's attribute setters.

@mcmire
Copy link
Collaborator

mcmire commented Oct 26, 2015

@joshteng No assumption is being made about your database's data type here. Because you qualified the matcher with only_integer, it's going to perform its subtests using floats instead of integers (which is what it normally uses). The idea is that since you're saying that the values this attribute is storing must be integers, if floats are used instead, then the record should fail validation. However, this is impossible to test when the column itself is an integer, because the float the matcher uses (in this case 0.1) will get changed into an integer (0) when it's assigned to the attribute. Hence, this invalidates the test and the matcher has no choice but to abort.

As @betesh is saying, it turns out that if you're using an integer column, validating that the attribute disallows non-integers is redundant, since the column type already guarantees that the attribute is an integer. It can't be anything but, since ActiveRecord casts it to one. (It used to not be this way but the behavior was changed in Rails 4.2.) This actually isn't true -- the validation should still work regardless of the new typecasting behavior in Rails 4.2. So this is a bug.

tjgrathwell added a commit to railsbridge/bridge_troll that referenced this issue Oct 27, 2015
thoughtbot/shoulda-matchers#784 fixes the issues with
validate_numericality_of, though validate_uniqueness_of still
fails without some intense hinting about what values are
allowed for user_type
@ehannes
Copy link

ehannes commented Nov 26, 2015

Any news on this?

@mcmire
Copy link
Collaborator

mcmire commented Nov 27, 2015

@ehannes I'm working on fixing the additional issues with the numericality matcher right now, it's a little slow going but I am working on it. Sorry I don't have anything else.

@betesh
Copy link
Contributor

betesh commented Nov 29, 2015

@mcmire What was incorrect about your original comment?

scottyschup pushed a commit to scottyschup/bridge_troll that referenced this issue Mar 26, 2016
thoughtbot/shoulda-matchers#784 fixes the issues with
validate_numericality_of, though validate_uniqueness_of still
fails without some intense hinting about what values are
allowed for user_type
@Xosmond
Copy link

Xosmond commented Jan 16, 2017

I have the decimal issue. Any news?

When assign "asb" decimal go "0.0" and also when assign "nil" decimal go "0.0" so i dont know how to validate or test this.

After setting :total to ‹nil› -- which was read back as ‹#<BigDecimal:7fe9f2540658,'0.0',9(18)>› -- the matcher expected the Purchase to be invalid and to produce the validation error "no puede estar en blanco" on :total.

After setting :total to ‹"abcd"› -- which was read back as ‹#<BigDecimal:7fe9f5977c00,'0.0',9(9)>› -- the matcher expected the Purchase to be invalid and to produce the validation error "no es un número" on :total

@CaioBianchi
Copy link

CaioBianchi commented Mar 20, 2018

I'm having the same issue simply testing for numericality and only_integer

Model:
validates :size, numericality: { integer_only: true }

Spec:
it { should validate_numericality_of(:size).only_integer }

Result:

       Example did not properly validate that :size looks like an integer.
         After setting :size to ‹0.1› -- which was read back as ‹0› -- the
         matcher expected the Example to be invalid, but it was valid instead.

The issue seems to lie on the only_integer method.

@mcmire
Copy link
Collaborator

mcmire commented Apr 4, 2018

@CaioBianchi Would you mind creating a new issue for this? I don't want to keep adding things to this issue since it's closed.

netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Sep 23, 2018
# 3.1.2

### Deprecations

* This is the **last version** that supports Rails 4.0 and 4.1 and Ruby 2.0 and 2.1.

### Bug fixes

* When the `permit` matcher was used without `#on`, the controller did not use
  `params#require`, the params object was duplicated, and the matcher did not
  recognize the `#permit` call inside the controller. This behavior happened
  because the matcher overwrote double registries with the same parameter hash
  whenever ActionController::Parameters was instantiated.

  * *Commit: [44c019]*
  * *Issue: [#899]*
  * *Pull request: [#902]*

# 3.1.1

### Bug fixes

* Some matchers make use of ActiveSupport's `in?` method, but do not include the
  file where this is defined in ActiveSupport. This causes problems with
  projects using shoulda-matchers that do not include all of ActiveSupport by
  default. To fix this, replace `in?` with Ruby's builtin `include?`.

  * *Pull request: [#879]*

* `validate_uniqueness_of` works by creating a record if it doesn't exist, and
  then testing against a new record with various attributes set that are equal
  to (or different than) corresponding attributes in the existing record. In
  3.1.0 a change was made whereby when the uniqueness matcher is given a new
  record and creates an existing record out of it, it ensures that the record is
  valid before continuing on. This created a problem because if the subject,
  before it was saved, was empty and therefore in an invalid state, it could not
  effectively be saved. While ideally this should be enforced, doing so would be
  a backward-incompatible change, so this behavior has been rolled back.
  ([#880], [#884], [#885])

  * *Commit: [45de869]*
  * *Issues: [#880], [#884], [#885]*

* Fix an issue with `validate_uniqueness_of` + `scoped_to` when used against a
  model where the attribute has multiple uniqueness validations and each
  validation has a different set of scopes. In this case, a test written for the
  first validation (and its scopes) would pass, but tests for the other
  validations (and their scopes) would not, as the matcher only considered the
  first set of scopes as the *actual* set of scopes.

  * *Commit: [28bd9a1]*
  * *Issues: [#830]*

### Improvements

* Update `validate_uniqueness_of` so that if an existing record fails to be
  created because a column is non-nullable and was not filled in, raise an
  ExistingRecordInvalid exception with details on how to fix the test.

  * *Commit: [78ccfc5]*

[#879]: thoughtbot/shoulda-matchers#879
[45de869]: thoughtbot/shoulda-matchers@45de869
[#880]: thoughtbot/shoulda-matchers#880
[#884]: thoughtbot/shoulda-matchers#884
[#885]: thoughtbot/shoulda-matchers#885
[78ccfc5]: thoughtbot/shoulda-matchers@78ccfc5
[28bd9a1]: thoughtbot/shoulda-matchers@28bd9a1
[#830]: thoughtbot/shoulda-matchers#830

# 3.1.0

### Bug fixes

* Update `validate_numericality_of` so that submatchers are applied lazily
  instead of immediately. Previously, qualifiers were order-dependent, meaning
  that if you used `strict` before you used, say, `odd`, then `strict` wouldn't
  actually apply to `odd`. Now the order that you specify qualifiers doesn't
  matter.

  * *Source: [6c67a5e]*

* Fix `allow_value` so that it does not raise an AttributeChangedValueError
  (formerly CouldNotSetAttributeError) when used against an attribute that is an
  enum in an ActiveRecord model.

  * *Source: [9e8603e]*

* Add a `ignoring_interference_by_writer` qualifier to all matchers, not just
  `allow_value`. *This is enabled by default, which means that you should never
  get a CouldNotSetAttributeError again.* (You may get some more information if
  a test fails, however.)

  * *Source: [1189934], [5532f43]*
  * *Fixes: [#786], [#799], [#801], [#804], [#817], [#841], [#849], [#872],
    [#873], and [#874]*

* Fix `validate_numericality_of` so that it does not blow up when used against
  a virtual attribute defined in an ActiveRecord model (that is, an attribute
  that is not present in the database but is defined using `attr_accessor`).

  * *Source: [#822]*

* Update `validate_numericality_of` so that it no longer raises an
  IneffectiveTestError if used against a numeric column.

  * *Source: [5ed0362]*
  * *Fixes: [#832]*

[6c67a5e]: thoughtbot/shoulda-matchers@6c67a5e
[9e8603e]: thoughtbot/shoulda-matchers@9e8603e
[1189934]: thoughtbot/shoulda-matchers@1189934
[5532f43]: thoughtbot/shoulda-matchers@5532f43
[#786]: thoughtbot/shoulda-matchers#786
[#799]: thoughtbot/shoulda-matchers#799
[#801]: thoughtbot/shoulda-matchers#801
[#804]: thoughtbot/shoulda-matchers#804
[#817]: thoughtbot/shoulda-matchers#817
[#841]: thoughtbot/shoulda-matchers#841
[#849]: thoughtbot/shoulda-matchers#849
[#872]: thoughtbot/shoulda-matchers#872
[#873]: thoughtbot/shoulda-matchers#873
[#874]: thoughtbot/shoulda-matchers#874
[#822]: thoughtbot/shoulda-matchers#822
[5ed0362]: thoughtbot/shoulda-matchers@5ed0362
[#832]: thoughtbot/shoulda-matchers#832

### Features

* Add a new qualifier, `ignoring_case_sensitivity`, to `validate_uniqueness_of`.
  This provides a way to test uniqueness of an attribute whose case is
  normalized, either in a custom writer method for that attribute, or in a
  custom `before_validation` callback.

  * *Source: [#840]*
  * *Fixes: [#836]*

[#840]: thoughtbot/shoulda-matchers#840
[#836]: thoughtbot/shoulda-matchers#836

### Improvements

* Improve failure messages and descriptions of all matchers across the board so
  that it is easier to understand what the matcher was doing when it failed.
  (You'll see a huge difference in the output of the numericality and uniqueness
  matchers in particular.)

* Matchers now raise an error if any attributes that the matcher is attempting
  to set do not exist on the model.

  * *Source: [2962112]*

* Update `validate_numericality_of` so that it doesn't always run all of the
  submatchers, but stops on the first one that fails. Since failure messages
  now contain information as to what value the matcher set on the attribute when
  it failed, this change guarantees that the correct value will be shown.

  * *Source: [8e24a6e]*

* Continue to detect if attributes change incoming values, but now instead of
  immediately seeing a CouldNotSetAttributeError, you will only be informed
  about it if the test you've written fails.

  * *Source: [1189934]*

* Add an additional check to `define_enum_for` to ensure that the column that
  underlies the enum attribute you're testing is an integer column.

  * *Source: [68dd70a]*

* Add a test for `validate_numericality_of` so that it officially supports money
  columns.

  * *Source: [a559713]*
  * *Refs: [#841]*

[2962112]: thoughtbot/shoulda-matchers@2962112
[8e24a6e]: thoughtbot/shoulda-matchers@8e24a6e
[68dd70a]: thoughtbot/shoulda-matchers@68dd70a
[a559713]: thoughtbot/shoulda-matchers@a559713

# 3.0.1

### Bug fixes

* Fix `validate_inclusion_of` + `in_array` when used against a date or datetime
  column/attribute so that it does not raise a CouldNotSetAttributeError.
  ([#783], [8fa97b4])

* Fix `validate_numericality_of` when used against a numeric column so that it
  no longer raises a CouldNotSetAttributeError if the matcher has been qualified
  in any way (`only_integer`, `greater_than`, `odd`, etc.). ([#784], [#812])

### Improvements

* `validate_uniqueness_of` now raises a NonCaseSwappableValueError if the value
  the matcher is using to test uniqueness cannot be case-swapped -- in other
  words, if it doesn't contain any alpha characters. When this is the case, the
  matcher cannot work effectively. ([#789], [ada9bd3])

[#783]: thoughtbot/shoulda-matchers#783
[8fa97b4]: thoughtbot/shoulda-matchers@8fa97b4
[#784]: thoughtbot/shoulda-matchers#784
[#789]: thoughtbot/shoulda-matchers#789
[ada9bd3]: thoughtbot/shoulda-matchers@ada9bd3
[#812]: thoughtbot/shoulda-matchers#812

# 3.0.0

### Backward-incompatible changes

* We've dropped support for Rails 3.x, Ruby 1.9.2, and Ruby 1.9.3, and RSpec 2.
  All of these have been end-of-lifed. ([a4045a1], [b7fe87a], [32c0e62])

* The gem no longer detects the test framework you're using or mixes itself into
  that framework automatically. [History][no-auto-integration-1] has
  [shown][no-auto-integration-2] that performing any kind of detection is prone
  to bugs and more complicated than it should be.

  Here are the updated instructions:

  * You no longer need to say `require: false` in your Gemfile; you can
    include the gem as normal.
  * You'll need to add the following somewhere in your `rails_helper` (for
    RSpec) or `test_helper` (for Minitest / Test::Unit):

    ``` ruby
    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        # Choose a test framework:
        with.test_framework :rspec
        with.test_framework :minitest
        with.test_framework :minitest_4
        with.test_framework :test_unit

        # Choose one or more libraries:
        with.library :active_record
        with.library :active_model
        with.library :action_controller
        # Or, choose the following (which implies all of the above):
        with.library :rails
      end
    end
    ```

  ([1900071])

* Previously, under RSpec, all of the matchers were mixed into all of the
  example groups. This created a problem because some gems, such as
  [active_model_serializers-matchers], provide matchers that share the same
  name as some of our own matchers. Now, matchers are only mixed into whichever
  example group they belong to:

    * ActiveModel and ActiveRecord matchers are available only in model example
      groups.
    * ActionController matchers are available only in controller example groups.
    * The `route` matcher is available only in routing example groups.

  ([af98a23], [8cf449b])

* There are two changes to `allow_value`:

  * The negative form of `allow_value` has been changed so that instead of
    asserting that any of the given values is an invalid value (allowing good
    values to pass through), assert that *all* values are invalid values
    (allowing good values not to pass through). This means that this test which
    formerly passed will now fail:

    ``` ruby
    expect(record).not_to allow_value('good value', *bad_values)
    ```

    ([19ce8a6])

  * `allow_value` now raises a CouldNotSetAttributeError if in setting the
    attribute, the value of the attribute from reading the attribute back is
    different from the one used to set it.

    This would happen if the writer method for that attribute has custom logic
    to ignore certain incoming values or change them in any way. Here are three
    examples we've seen:

    * You're attempting to assert that an attribute should not allow nil, yet
      the attribute's writer method contains a conditional to do nothing if
      the attribute is set to nil:

      ``` ruby
      class Foo
        include ActiveModel::Model

        attr_reader :bar

        def bar=(value)
          return if value.nil?
          @bar = value
        end
      end

      describe Foo do
        it do
          foo = Foo.new
          foo.bar = "baz"
          # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
          expect(foo).not_to allow_value(nil).for(:bar)
        end
      end
      ```

    * You're attempting to assert that an numeric attribute should not allow a
      string that contains non-numeric characters, yet the writer method for
      that attribute strips out non-numeric characters:

      ``` ruby
      class Foo
        include ActiveModel::Model

        attr_reader :bar

        def bar=(value)
          @bar = value.gsub(/\D+/, '')
        end
      end

      describe Foo do
        it do
          foo = Foo.new
          # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
          expect(foo).not_to allow_value("abc123").for(:bar)
        end
      end
      ```

    * You're passing a value to `allow_value` that the model typecasts into
      another value:

      ``` ruby
      describe Foo do
        # Assume that `attr` is a string
        # This will raise a CouldNotSetAttributeError since `attr` typecasts `[]` to `"[]"`
        it { should_not allow_value([]).for(:attr) }
      end
      ```

    With all of these failing examples, why are we making this change? We want
    to guard you (as the developer) from writing a test that you think acts one
    way but actually acts a different way, as this could lead to a confusing
    false positive or negative.

    If you understand the problem and wish to override this behavior so that
    you do not get a CouldNotSetAttributeError, you can add the
    `ignoring_interference_by_writer` qualifier like so. Note that this will not
    always cause the test to pass.

    ``` ruby
    it { should_not allow_value([]).for(:attr).ignoring_interference_by_writer }
    ```

    ([9d9dc4e])

* `validate_uniqueness_of` is now properly case-sensitive by default, to match
  the default behavior of the validation itself. This is a backward-incompatible
  change because this test which incorrectly passed before will now fail:

    ``` ruby
    class Product < ActiveRecord::Base
      validates_uniqueness_of :name, case_sensitive: false
    end

    describe Product do
      it { is_expected.to validate_uniqueness_of(:name) }
    end
    ```

    ([57a1922])

* `ensure_inclusion_of`, `ensure_exclusion_of`, and `ensure_length_of` have been
  removed in favor of their `validate_*` counterparts. ([55c8d09])

* `set_the_flash` and `set_session` have been changed to more closely align with
  each other:
  * `set_the_flash` has been removed in favor of `set_flash`. ([801f2c7])
  * `set_session('foo')` is no longer valid syntax, please use
    `set_session['foo']` instead. ([535fe05])
  * `set_session['key'].to(nil)` will no longer pass when the key in question
    has not been set yet. ([535fe05])

* Change `set_flash` so that `set_flash[:foo].now` is no longer valid syntax.
  You'll want to use `set_flash.now[:foo]` instead. This was changed in order to
  more closely align with how `flash.now` works when used in a controller.
  ([#755], [#752])

* Change behavior of `validate_uniqueness_of` when the matcher is not
  qualified with any scopes, but your validation is. Previously the following
  test would pass when it now fails:

  ``` ruby
  class Post < ActiveRecord::Base
    validate :slug, uniqueness: { scope: :user_id }
  end

  describe Post do
    it { should validate_uniqueness_of(:slug) }
  end
  ```

  ([6ac7b81])

[active_model_serializers-matchers]: https:/adambarber/active_model_serializers-matchers
[no-auto-integration-1]: freerange/mocha@049080c
[no-auto-integration-2]: rr/rr#29
[1900071]: thoughtbot/shoulda-matchers@1900071
[b7fe87a]: thoughtbot/shoulda-matchers@b7fe87a
[a4045a1]: thoughtbot/shoulda-matchers@a4045a1
[57a1922]: thoughtbot/shoulda-matchers@57a1922
[19ce8a6]: thoughtbot/shoulda-matchers@19c38a6
[eaaa2d8]: thoughtbot/shoulda-matchers@eaaa2d8
[55c8d09]: thoughtbot/shoulda-matchers@55c8d09
[801f2c7]: thoughtbot/shoulda-matchers@801f2c7
[535fe05]: thoughtbot/shoulda-matchers@535fe05
[6ac7b81]: thoughtbot/shoulda-matchers@6ac7b81
[#755]: thoughtbot/shoulda-matchers#755
[#752]: thoughtbot/shoulda-matchers#752
[9d9dc4e]: thoughtbot/shoulda-matchers@9d9dc4e
[32c0e62]: thoughtbot/shoulda-matchers@32c0e62
[af98a23]: thoughtbot/shoulda-matchers@af98a23
[8cf449b]: thoughtbot/shoulda-matchers@8cf449b

### Bug fixes

* So far the tests for the gem have been running against only SQLite. Now they
  run against PostgreSQL, too. As a result we were able to fix some
  Postgres-related bugs, specifically around `validate_uniqueness_of`:

  * When scoped to a UUID column that ends in an "f", the matcher is able to
    generate a proper "next" value without erroring. ([#402], [#587], [#662])

  * Support scopes that are PostgreSQL array columns. Please note that this is
    only supported for Rails 4.2 and greater, as versions before this cannot
    handle array columns correctly, particularly in conjunction with the
    uniqueness validator. ([#554])

  * Fix so that when scoped to a text column and the scope is set to nil before
    running it through the matcher, the matcher does not fail. ([#521], [#607])

* Fix `define_enum_for` so that it actually tests that the attribute is present
  in the list of defined enums, as you could fool it by merely defining a class
  method that was the pluralized version of the attribute name. In the same
  vein, passing a pluralized version of the attribute name to `define_enum_for`
  would erroneously pass, and now it fails. ([#641])

* Fix `permit` so that it does not break the functionality of
  ActionController::Parameters#require. ([#648], [#675])

* Fix `validate_uniqueness_of` + `scoped_to` so that it does not raise an error
  if a record exists where the scoped attribute is nil. ([#677])

* Fix `route` matcher so if your route includes a default `format`, you can
  specify this as a symbol or string. ([#693])

* Fix `validate_uniqueness_of` so that it allows you to test against scoped
  attributes that are boolean columns. ([#457], [#694])

* Fix failure message for `validate_numericality_of` as it sometimes didn't
  provide the reason for failure. ([#699])

* Fix `shoulda/matchers/independent` so that it can be required
  independently, without having to require all of the gem. ([#746], [e0a0200])

### Features

* Add `on` qualifier to `permit`. This allows you to make an assertion that
  a restriction was placed on a slice of the `params` hash and not the entire
  `params` hash. Although we don't require you to use this qualifier, we do
  recommend it, as it's a more precise check. ([#675])

* Add `strict` qualifier to `validate_numericality_of`. ([#620])

* Add `on` qualifier to `validate_numericality_of`. ([9748869]; h/t [#356],
  [#358])

* Add `join_table` qualifier to `have_and_belong_to_many`. ([#556])

* `allow_values` is now an alias for `allow_value`. This makes more sense when
  checking against multiple values:

  ``` ruby
  it { should allow_values('this', 'and', 'that') }
  ```

  ([#692])

[9748869]: thoughtbot/shoulda-matchers@9748869
[#402]: thoughtbot/shoulda-matchers#402
[#587]: thoughtbot/shoulda-matchers#587
[#662]: thoughtbot/shoulda-matchers#662
[#554]: thoughtbot/shoulda-matchers#554
[#641]: thoughtbot/shoulda-matchers#641
[#521]: thoughtbot/shoulda-matchers#521
[#607]: thoughtbot/shoulda-matchers#607
[#648]: thoughtbot/shoulda-matchers#648
[#675]: thoughtbot/shoulda-matchers#675
[#677]: thoughtbot/shoulda-matchers#677
[#620]: thoughtbot/shoulda-matchers#620
[#693]: thoughtbot/shoulda-matchers#693
[#356]: thoughtbot/shoulda-matchers#356
[#358]: thoughtbot/shoulda-matchers#358
[#556]: thoughtbot/shoulda-matchers#556
[#457]: thoughtbot/shoulda-matchers#457
[#694]: thoughtbot/shoulda-matchers#694
[#692]: thoughtbot/shoulda-matchers#692
[#699]: thoughtbot/shoulda-matchers#699
[#746]: thoughtbot/shoulda-matchers#746
@ghost
Copy link

ghost commented Jan 11, 2021

any update?

@mcmire
Copy link
Collaborator

mcmire commented Jan 11, 2021

@mohsen-alizadeh-clark2 The original issue was fixed in 18b2859 and a0f1221. If you are still having a problem, please make a new issue with an example of your database schema, code, and tests so I can track it better!

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

No branches or pull requests