Why does RSpec fail on that validation? - ruby-on-rails

I have these validations :
it "should have 100 adventure points" do
user = User.new
user.adventure_points.should == 100
end
it "should be level 1" do
user = User.new
user.level.should == 1
end
it "should have 10 sapphires" do
user = User.new
user.sapphires.should == 10
end
Then, i also have in my migration :
t.string :name, :limit => 20
t.integer :strength_points, :default => 0
t.integer :dexterity_points, :default => 0
t.integer :magic_points, :default => 0
t.integer :accuracy_points, :default => 0
t.integer :health_points, :default => 0
t.integer :level, :default => 1
t.integer :adventure_points, :default => 100
t.integer :sapphires, :default => 10
t.integer :duel_wins, :default => 0
t.integer :duel_losses, :default => 0
t.string :image_url
t.integer :strength
When i run rspec i get :
A new User
is not valid without a name
is not valid without a first class
is not valid without a password
should have 100 adventure points
should be level 1 (FAILED - 1)
should have 10 sapphires (FAILED - 2)
Failures:
1) A new User should be level 1
Failure/Error: user.level.should == 1
expected: 1
got: nil (using ==)
# ./spec/models/user_spec.rb:28
2) A new User should have 10 sapphires
Failure/Error: user.sapphires.should == 10
expected: 10
got: nil (using ==)
# ./spec/models/user_spec.rb:33
Why does it present these errors on level, sapphires, but it works ok for adventure_points ? Moreover, if i open a console and do User.new, i get the default values as expected. What is hapening here ?

The database defaults are not set until the model is saved. Try:
it "should have 100 adventure points" do
user = User.create
user.adventure_points.should == 100
end

Found the solution myself, pretty sneaky one. I had to run rake:test:prepare because the test database did not have fixtures data and was not prepared :P

Related

FactoryBot unique constraint violation error, when specifying id in the factory

When a record is created with specified id, subsequent create call fails with unique constraint violation error.
Output:
[1]> create(:user, id: 1)
=> #<User:0x00005615116f7378id: 1,created_at: Thu, 23 Apr 2020 08:21:04 CEST +02:00,updated_at: Thu, 23 Apr 2020 08:21:04 CEST +02:00...
[2]> create(:user)
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "users_pkey"DETAIL: Key (id)=(1) already exists.
I would expect FactoryBot to pick up from the last created id and continue on creating. :) What am I missing here? Please advise.
FactoryBot version(as taken from the Gemfile):
factory_bot_rails (5.1.1)
factory_bot (5.1.2)
[edit]
spec/factories/users.rb
FactoryBot.define do
factory :user do
first_name { "Max" }
last_name { "Mustermann" }
email_addresses { build_list(:email_address, 1) }
password { TEST_USER_PASSWORD }
accepted_terms_of_use { true }
end
end
from db/schema.rb
create_table "users", id: :serial, force: :cascade do |t|
t.string "first_name", limit: 255
t.string "last_name", limit: 255
t.string "password_digest", limit: 255
t.boolean "accepted_terms_of_use", default: false, null: false
end
add_foreign_key "email_addresses", "users"
I would expect FactoryBot to pick up from the last created id and
continue on creating. :) What am I missing here?
ID sequences are handed by the database - not FactoryBot. FactoryBot only assigns ids when you use build_stubbed to fake persistence.
The solution is really simple - don't hardcode ids. Instead assign your factories to let's / variables and reference them.
Bad:
let(:user) { create(:user, id: 1) }
it "does something" do
get "/users/1"
end
Good:
let(:user) { create(:user) }
it "does something" do
get user_path(user)
end

Ruby on Rails decimal comparison stopped working

I have been using a state object in the database that keeps track of what seed data has been loaded into it. The structure of the table is:
create_table "toolkit_states", force: :cascade do |t|
t.boolean "signups", default: true
t.decimal "database_version", precision: 5, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The seeds.rb file checks the database_version and runs blocks of code and then sets the database_version after running the block. It has worked fine from versions 0.1 up to 0.55.
I added a new block of seed data. To run that block the database_version is checked and it should be 0.56. The following comparison does not work:
if state.database_version == 0.56
For some reason, the number 0.56 cannot be evaluated for equality with the value stored in the database. It has worked on all the values up to 0.56.
Here is a rails console session:
irb(main):001:0> state = ToolkitState.first
ToolkitState Load (0.4ms) SELECT "toolkit_states".* FROM "toolkit_states" ORDER BY "toolkit_states"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<ToolkitState id: 1, signups: false, database_version: 0.56e0, created_at: "2018-12-27 17:04:50", updated_at: "2018-12-27 17:04:56">
irb(main):002:0> state.database_version == 0.56
=> false
irb(main):003:0> state.database_version == 0.56e0
=> false
irb(main):004:0> state.database_version == 0.56.to_f
=> false
irb(main):005:0> state.database_version.to_f == 0.56
=> true
When I convert the value with a "to_f", the comparison works. My problem is that it as worked well without this conversion up to the value, 0.56
It occurs because state.database_version is an instance of BigDecimal class. This article explain why it is BigDecimal.
Look at this example:
BigDecimal('0.56e0')
=> 0.56e0
irb(main):008:0> BigDecimal('0.56e0') == 0.56
=> false
irb(main):009:0> BigDecimal('0.56e0').to_f
=> 0.56
As you can see 0.56e0 after transformation to float type becomes 0.56 and your comparison returns true.
Nate explained more briefly why it's happening in this comment.
irb(main):001:0> c = BigDecimal('0.56e0')
=> 0.56e0
irb(main):002:0> c == 0.56
=> false
irb(main):003:0> c = BigDecimal('0.55e0')
=> 0.55e0
irb(main):004:0> c == 0.55
=> true
Works for 0.55 and not for 0.56 Rails bug?

Why is Rails setting my attribute to nil on create?

Ruby 2.2.0 on Rails 4.2.2
I'm attempting to write a custom date validator (that is going to be expanded to handle some other cases once I get this part working), but right now it's failing to appropriately validate (and return false) on strings - it doesn't even run. It seems that rails is completely ignoring the date_ended value when it's set to a string. When I try the same test except with an integer it correctly validates and fails that validation. If I don't allow nil values, the validator correctly prevents record creation on a string value, but only because it rejects the nil value. Any and all suggestions are appreciated.
The same exact problem exists for date_started.
Edit: I've confirmed that the validation is failing on the second expectation and not the first validation by double-checking that CommitteeMember.count is 0 before the first expectation.
CommitteeMember:
class CommitteeMember < ActiveRecord::Base
belongs_to :committee
belongs_to :member
validates :committee_id, presence: true
validates :member_id, uniqueness: { scope: :committee_id }, presence: true
validates :date_started, date: true, allow_nil: true
validates :date_ended, date: true, allow_nil: true
end
schema.rb relevant lines:
create_table "committee_members", force: :cascade do |t|
t.integer "committee_id", null: false
t.integer "member_id", null: false
t.date "date_started"
t.date "date_ended"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
DateValidator (custom validator):
(note the printed value in the middle)
class DateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
puts "Validating #{value}"
unless value.kind_of?(Date)
record.errors[attribute] << "must be of type date"
end
end
end
CommiteeMember relevant spec:
(note the printed value in the middle)
it 'should fail with a string for date_ended' do
expect(CommitteeMember.count).to eq(0)
CommitteeMember.create!(member_id: 1, committee_id: 1, date_ended: "S")
ap CommitteeMember.first
expect(CommitteeMember.count).to eq(0)
end
Spec Output:
$ rspec spec/models/committee_member_spec.rb
......#<CommitteeMember:0x00000007d1f888> {
:id => 1,
:committee_id => 1,
:member_id => 1,
:date_started => nil,
:date_ended => nil,
:created_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00,
:updated_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00
}
F
Failures:
1) CommitteeMember validations should fail with a string for date_ended
Failure/Error: expect(CommitteeMember.count).to eq(0)
expected: 0
got: 1
(compared using ==)
# ./spec/models/committee_member_spec.rb:52:in `block (3 levels) in <top (required)>'
Finished in 0.54864 seconds (files took 2.43 seconds to load)
7 examples, 1 failure
Failed examples:
rspec ./spec/models/committee_member_spec.rb:48 # CommitteeMember validations should fail with a string for date_ended
Since the attributes are date attributes, Rails automatically parses any values that you attempt to assign. If it cannot parse as a date, it will leave the value as nil, e.g. in the console:
c = CommitteeMember.new
c.date_started = 'S'
=> "S"
c.date_started
=> nil
In fact, Rails will actually parse a string into a Date:
c.date_started = '2016-1-1'
=> "2016-1-1"
c.date_started
=> Fri, 01 Jan 2016
c.date_started.class
=> Date
This means that you don't need to validate that your date fields are dates at all, because Rails won't store them otherwise. Instead, just validate that they exist:
validates_presence_of :date_started, :date_ended

spree_products_taxons record creating in RoR: Spree

I want to create new record in spree_products_taxons table with ActiveRecord (not with pure SQL), but:
1.9.3-head :003 > Spree::ProductsTaxon.create(product_id: 666, taxon_id: 777)
NameError: uninitialized constant Spree::ProductsTaxon
Where am i wrong?
ps. In my schema file:
create_table "spree_products_taxons", :id => false, :force => true do |t|
t.integer "product_id"
t.integer "taxon_id"
end
You can try something like this
product = Spree::Product.find(666)
taxon = Spree::Taxon.find(777)
product.taxons << taxon
product.save
taxons = product.taxons

Rails: using binary value to query a binary column returns nothing

I have a binary column which contains 256-bit checksums. I can store the checksums ok, but when I try to query via the checksum, nothing is returned.
d = Digest::SHA2.new
d.update "large str i'm creating the hash with"
begin
codebase = Codebase.find_or_create_by_checksum(d.digest)
rescue ActiveRecord::StatementInvalid => e
# handle duplicate record error
end
I've tried where and different versions of find. Nothing returns. When I use find_or_create_by_checksum, since it doesn't find anything it tries to create it and an exception is raised since I have a uniq index on the checksum column, but still I need to be able to get the record with the matching checksum.
create_table :codebases do |t|
t.binary :checksum, :null => false, :limit => 32
end
add_index :codebases, :checksum, :unique => true, :name => 'name_of_the_codebas_uniq_index'
Anybody know how to do this?
So if its binary on the database, I couldn't reproduce:
migration:
class Checksum < ActiveRecord::Migration
def up
create_table :checksums do |t|
t.binary :checksum, :null => false, :limit => 32
end
end
def down
end
end
And then trying it on the rails console:
ruby-1.9.2-p290 :009 > Checksum.create(:checksum => digest.digest)
SQL (0.4ms) INSERT INTO "checksums" ("checksum") VALUES (?) [["checksum", ",&\xB4kh\xFF\xC6\x8F\xF9\x9BE<\x1D0A4\x13B-pd\x83\xBF\xA0\xF9\x8A^\x88bf\xE7\xAE"]]
=> #<Checksum id: 1, checksum: ",&\xB4kh\xFF\xC6\x8F\xF9\x9BE<\x1D0A4\x13B-pd\x83\xBF\xA0\xF9\x8A^\x88bf\xE7\xAE">
ruby-1.9.2-p290 :010 > Checksum.first
Checksum Load (0.2ms) SELECT "checksums".* FROM "checksums" LIMIT 1
=> #<Checksum id: 1, checksum: ",&\xB4kh\xFF\xC6\x8F\xF9\x9BE<\x1D0A4\x13B-pd\x83\xBF\xA0\xF9\x8A^\x88bf\xE7\xAE">
ruby-1.9.2-p290 :011 > Checksum.find_by_checksum(digest.digest)
Checksum Load (0.1ms) SELECT "checksums".* FROM "checksums" WHERE "checksums"."checksum" = x'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae' LIMIT 1
=> #<Checksum id: 1, checksum: ",&\xB4kh\xFF\xC6\x8F\xF9\x9BE<\x1D0A4\x13B-pd\x83\xBF\xA0\xF9\x8A^\x88bf\xE7\xAE">
So it works as expected.....

Resources