Ruby on Rails decimal comparison stopped working - ruby-on-rails

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?

Related

Searching UNIX timestamp gives differing results

The following queries give different results, the result for both should be two. I'm using timestamp columns in my db (postgres), and am searching for objects where their end_at column is less than or equal to a given UNIX timestamp.
puts object.time_records.where('time_records.end_at <= ?', object.time_records.second.end_at).count #=> 2 (Correct)
puts object.time_records.where('time_records.end_at <= ?', DateTime.strptime(object.time_records.second.end_at.to_i.to_s, '%s')).count # => 1 (Incorrect)
puts object.time_records.where('time_records.end_at <= ?', Time.at(object.time_records.second.end_at.to_i)).count # => 1 (Incorrect)
If I seed some data, the timestamp used in the query might be, for example:
1473024092
Then if I print the timestamps for the object:
puts object.time_records.pluck(:end_at).map(&:to_i)
I get the following results:
1472419292
1473024092
1473628892
1474233692
As can be seen from these, the correct result should be two. If anyone has encountered something similar I'd appreciate a pointer in the right direction.
For what it's worth, this is occurring in specs I'm writing for a gem. I've tried varying combinations of in_time_zone and .utc for parsing and converting to the timestamp, and they all offer the same result. Even converting to a timestamp and straight back to a Time, and testing for equality results in false, when to_s is equal for both.
I ran an example in irb:
2.3.0 :001 > now = Time.now
=> 2016-08-28 21:58:43 +0100
2.3.0 :002 > timestamp = now.to_i
=> 1472417923
2.3.0 :003 > parsed_timestamp = Time.at(timestamp)
=> 2016-08-28 21:58:43 +0100
2.3.0 :004 > now.eql?(parsed_timestamp)
=> false
2.3.0 :005 > now == parsed_timestamp
=> false
2.3.0 :006 > now === parsed_timestamp
=> false
2.3.0 :007 > now.class
=> Time
2.3.0 :008 > parsed_timestamp.class
=> Time
The issue was fractional times. UNIX timestamps are to the second, so when converting to_i, the milliseconds are discarded.
Setting the precision of the timestamp columns resolved this issue:
class CreateTimeRecords < ActiveRecord::Migration
def change
create_table :time_records do |t|
t.belongs_to :object, index: true, null: false
t.datetime :start_at, null: false, index: true, precision: 0
t.datetime :end_at, null: false, index: true, precision: 0
t.timestamps null: false
end
end
end

Rails: boolean attr_accessible default method behavior

I have a normal class (Event) where a boolean value is defined as
attr_accessible :archived
and in the db schema is
t.boolean "archived", :default => false
As such, the default method archived? is automatically defined.
After a debugging session I discovered this
>> a=Event.last
>> a.archived
false
>> a.archived=true
true
>> a.archived
true
>> a.archived?
false
Should't the last two values BOTH be true? What am I missing here?
Even this is not helping:
>> a.save
(0.1ms) begin transaction
(45.2ms) UPDATE "events" SET "archived" = 't', "updated_at" = '2012-12-10 06:31:57.410990' WHERE "events"."id" = 3
true
(149.5ms) commit transaction
>> a.archived?
false
Thank you in advance

"PG::Error - numeric field overflow" on Heroku

I have built an app that queries Google Analytics for the last 7 days of data. Everything works locally. On Heroku, the process runs smoothly until it tries to get data for today's date. I then get the following error:
2012-10-29T02:32:02+00:00 app[web.1]: ActiveRecord::StatementInvalid (PG::Error: ERROR: numeric field overflow
2012-10-29T02:32:02+00:00 app[web.1]: DETAIL: A field with precision 8, scale 2 must round to an absolute value less than 10^6.
I have tried to figure out which variable it's not happy with but I don't know right now. I am assuming it's something related to date or time.
Any thoughts or ideas would be great :)
-- update ---
ActiveRecord::Schema.define(:version => 20121014153338) do
create_table "analytics", :force => true do |t|
t.string "site"
t.integer "visits"
t.date "start_date"
t.date "end_date"
t.decimal "revenue_per_transaction", :precision => 8, :scale => 2
t.integer "transactions"
t.decimal "item_quantity", :precision => 8, :scale => 2
t.integer "goal_starts"
t.integer "goal_completes"
t.decimal "goal_conversion", :precision => 8, :scale => 2
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.decimal "goal_abandon", :precision => 8, :scale => 2
t.decimal "revenue", :precision => 8, :scale => 2
t.string "source"
end
end
You have a numeric field with typmod numeric(8,2) and you're trying to store a value greater than 999999.99 in it. See the PostgreSQL manual on NUMERIC for information on numeric scale and precision, which are the qualifiers shown after the type in parentheses.
This earlier question appears to cover the same issue with Rails, showing the Rails model and how the scale and precision are assigned.
NUMERIC isn't a date/time field, it's a number field.
Demo of the issue:
regress=> SELECT NUMERIC(8,2) '999999.99';
numeric
-----------
999999.99
(1 row)
regress=> SELECT NUMERIC(8,2) '1000000.00';
ERROR: numeric field overflow
DETAIL: A field with precision 8, scale 2 must round to an absolute value less than 10^6.
It's a pity that Pg doesn't tell you what field this is when it is a field. It's difficult for it to do so, though, because it doesn't usually know which value is going to go into which field when it's parsing string literals. Enable log_statement = 'all' in postgresql.conf, ALTER USER ... SET, ALTER DATABASE ... SET, or per-session with SET log_statement = 'all' then re-test and examine the query logs.
Also look at the table definitions with \dt in psql to see what might have the type numeric(8,2) and could be causing the problem.
As for why it works locally: Is the local DB PostgreSQL? Some Rails users seem to have a very odd setup where they use SQLite locally, and PostgreSQL on Heroku. This is a recipe for chaos and deployment problems. Use the same database in development and testing. If it is PostgreSQL locally, is it the same version?

rspec - why does this array key comparison fail?

Failure/Error: #group.attributes.keys.should include (Group.first.attributes.keys)
expected
["id", "duration", "frequency", "period", "group_size", "location", "service", "area_of_need", "created_at", "updated_at", "therapist_id", "start_date", "end_date", "student_id", "adhoc"]
to include
["id", "duration", "frequency", "period", "group_size", "location", "service", "area_of_need", "created_at", "updated_at", "therapist_id", "start_date", "end_date", "student_id", "adhoc"]
Test:
#group.attributes.keys.should include (Group.first.attributes.keys)
Because a.should includes(b) asserts that a.include? b is true andinclude? checks to see if an array's elements includes an object, not whether one array equals another
[1].include? [1]
=> false
[1].include? 1
=> true
[[1]].include? [1]
=> true
Te answer was that the hash comparison fails on Ubuntu but works on Mac's.
My workaround is:
i=0
while i < #group.attributes.count
assert_equal #group.attributes[i], Group.first.attributes[i]
i+= 1
end
# Comparing field by field as ruby hash comparison isn't working right -
# but only on Ubuntu!

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