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
Related
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?
I have an array of hashes saved to a Rails 5 Postgres DB (Ruby 2.3.1). I’m able to display this on my show.html.erb page like so:
<%= #item.yearly_interest_totals %>
This displays:
[
"{:financial_year=>\"2017\", :total=>\"120.08\"}",
"{:financial_year=>\"2018\", :total=>\"237.32\"}",
"{:financial_year=>\"2019\", :total=>\"163.75\"}",
"{:financial_year=>\"2020\", :total=>\"87.95\"}",
"{:financial_year=>\"2021\", :total=>\"15.38\"}"
]
Also on this page I have a variable <%= fin_year %> which displays 2017.
I’m trying to display the value corresponding to this fin_year key in the view, with the following code, but it is giving me a no implicit conversion of Symbol into Integer error…
<%= #item.yearly_interest_totals.detect do |t|
t[:financial_year] == fin_year end [:total] %>
Could somebody please explain why i'm receiving this error?
Update
Both the hash key and local variable being named the same is confusing, I have changed the local variable to fin_year.
<%= fin_year.class %> is producing String
<%= #item.yearly_interest_totals.class %> is producing Array
<%= #item.yearly_interest_totals[0][:financial_year].class %> is returning a "no implicit conversion of Symbol into Integer" error...
The problem appears to be that the values for the keys :financial_year in your array of hashes are strings (e.g. "2017"), but your value for the variable financial_year is a fixnum/integer (e.g 2017). Try making them consistent to compare, such as:
<%= #item.yearly_interest_totals.detect do |t|
t[:financial_year] == financial_year.to_s end [:total] %>
Here is output from the Rails console comparing the two:
Running via Spring preloader in process 15647
Loading development environment (Rails 4.2.7.1)
2.3.3 :001 > item_yearly_interest_totals = [{ financial_year: "2017", total: "120.08" }, { financial_year: "2018", total: "237.32" }, { financial_year: "2019", total: "163.75" }, { financial_year: "2020", total: "87.95" }, { financial_year: "2021", total: "15.38" }]
=> [{:financial_year=>"2017", :total=>"120.08"}, {:financial_year=>"2018", :total=>"237.32"}, {:financial_year=>"2019", :total=>"163.75"}, {:financial_year=>"2020", :total=>"87.95"}, {:financial_year=>"2021", :total=>"15.38"}]
2.3.3 :002 > financial_year = 2017
=> 2017
2.3.3 :003 > item_yearly_interest_totals.detect do |t|
2.3.3 :004 > t[:financial_year] == financial_year end [:total]
NoMethodError: undefined method `[]' for nil:NilClass
.
.
.
2.3.3 :005 > item_yearly_interest_totals.detect do |t|
2.3.3 :006 > t[:financial_year] == financial_year.to_s end [:total]
=> "120.08"
2.3.3 :007 >
UPDATE (02-20-2017)
I don't completely understand where the distinction within Rails lies or is occurring that is the source of your issue, but even though you execute #item.yearly_interest_totals[0].class and you get Hash, you can't seem to access the values using a hash key (e.g. [:financial_year], ["financial_year"], etc.).
After some digging, I found this:
Rails access hash value
and the accepted answer led me to try JSON.parse, which I was able to get working, albeit with .each rather than .detect. This time I did, in a Rails 5 app, create an Item model, used Postgres, and seeded a single Item. What I still did not do is create a controller or any views. I executed my code through the Rails console. So, if you duplicate my code and it does not work for you, the problem may lie there, within the controller and views.
Ultimately, there is still some discovery to be done regarding this hash/JSON distinction and how implementation leads it to manifest as one or the other.
app/models/item.rb
class Item < ApplicationRecord
validates :name, presence: true
end
db/migrate/20170220221004_enable_hstore_extension.rb
class EnableHstoreExtension < ActiveRecord::Migration
def change
enable_extension 'hstore'
end
end
db/migrate/20170220221129_create_item.rb
class CreateItem < ActiveRecord::Migration[5.0]
def change
create_table :items do |t|
t.string :name, null: false, index: { unique: true }
t.hstore :yearly_interest_totals, array: true
t.timestamps null: false
end
end
end
db/seeds.rb
Item.create(name: 'Sample Item', yearly_interest_totals: [{ financial_year: "2017", total: "120.08" }, { financial_year: "2018", total: "237.32" }, { financial_year: "2019", total: "163.75" }, { financial_year: "2020", total: "87.95" }, { financial_year: "2021", total: "15.38" }])
And here is the code as it is executed in the Rails console:
Running via Spring preloader in process 19764
Loading development environment (Rails 5.0.1)
2.4.0 :001 > #item = Item.first
Item Load (1.4ms) SELECT "items".* FROM "items" ORDER BY "items"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Item id: 1, name: "Sample Item", yearly_interest_totals: [{"total"=>"120.08", "financial_year"=>"2017"}, {"total"=>"237.32", "financial_year"=>"2018"}, {"total"=>"163.75", "financial_year"=>"2019"}, {"total"=>"87.95", "financial_year"=>"2020"}, {"total"=>"15.38", "financial_year"=>"2021"}], created_at: "2017-02-20 22:25:14", updated_at: "2017-02-20 22:25:14">
2.4.0 :002 > #item.class
=> Item(id: integer, name: string, yearly_interest_totals: hstore, created_at: datetime, updated_at: datetime)
2.4.0 :003 > #item.yearly_interest_totals.class
=> Array
2.4.0 :004 > #item.yearly_interest_totals[0].class
=> Hash
2.4.0 :005 > financial_year = 2017
=> 2017
2.4.0 :006 > financial_year.class
=> Integer
2.4.0 :007 > selected_year_interest_total = nil
=> nil
2.4.0 :008 > selected_year_interest_total.class
=> NilClass
2.4.0 :009 > #item.yearly_interest_totals.each do |t|
2.4.0 :010 > puts JSON.parse(t["financial_year"]).class
2.4.0 :011 > if JSON.parse(t["financial_year"]) == financial_year
2.4.0 :012?> selected_year_interest_total = JSON.parse(t["total"])
2.4.0 :013?> end
2.4.0 :014?> end
Integer
Integer
Integer
Integer
Integer
=> [{"total"=>"120.08", "financial_year"=>"2017"}, {"total"=>"237.32", "financial_year"=>"2018"}, {"total"=>"163.75", "financial_year"=>"2019"}, {"total"=>"87.95", "financial_year"=>"2020"}, {"total"=>"15.38", "financial_year"=>"2021"}]
2.4.0 :015 > selected_year_interest_total
=> 120.08
2.4.0 :016 > selected_year_interest_total.class
=> Float
I dunno about Rails 5 but maybe this will help, Rails 4, assuming that financial_year is a variable and I am understanding the question correctly:
<% #item.yearly_interest_totals.each do |t| %>
<%= t['total'] == financial_year %>
<% end %>
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
I have a model called Day that represents a day in a timesheet. I've noticed that whenever I call #day.save it's writing to the database, even though none of the object's properties have had their values changed.
#day = Day.last
=> #<Day lunch_minutes: 0, updated_at: "2012-08-19 12:09:40", work_hours: 5.5>
A day has its length in hours, and the length of its lunch break in minutes, stored. I've cropped out some properties that aren't relevant.
#day.lunch_minutes
=> 0
#day.lunch_minutes = 0
=> 0
#day.changes
=> {"lunch_minutes"=>[0, 0]}
#day.lunch_minutes_changed?
=> true
That should be false. Compare to a value that isn't zero:
#day.work_hours = 5.5
=> 5.5
#day.work_hours_changed?
=> false
So if I call save, this gets called. Ideally there would be no unnecessary database interaction here.
#day.save
(0.5ms) UPDATE "days" SET "lunch_minutes" = 0, "updated_at" = '2012-08-19 12:22:59.586860' WHERE "days"."id" = 48
I'm not sure if this is a Rails bug or if I'm doing some incorrectly somewhere. It looks like it could be an issue in "changes_from_zero_to_string?" - I think adding a && value != 0 to that method would fix it - but I want to know if anyone else has seen this/a fix for this before?
What version of rails are you using? I just had a go in my app (3.1.5/1.8.7) and it doesn't behave this way.. I just used a random integer property on one of my models to test with:
1.8.7 :006 > o = Order.first
=> <Order id:...>
1.8.7 :007 > o.order_items_count
=> 0
1.8.7 :008 > o.order_items_count = 0
=> 0
1.8.7 :009 > o.changes
=> {}
1.8.7 :010 > o.order_items_count = '0'
=> "0"
1.8.7 :011 > o.changes
=> {}
1.8.7 :012 > o.save
(0.1ms) BEGIN
(0.1ms) COMMIT
=> true
It appears to be a bug.
Interestingly, according to the code, if you do:
#day.lunch_minutes = '0'
It will probably think it has not changed!
Try that, and if indeed this change causes #day.lunch_minutes_changed? to be false, then make sure it is reported as an issue to https://github.com/rails/rails.
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.....