Ordering by created_at in unit tests with generated data in rails
I have a bit of code that basically displays the last x (variable, but let's say x is 20 here) updates made in a given table. In one of the unit tests for it, 开发者_高级运维I have this snippet:
EditedItem.push_to_queue(hiddennow)
#create some new entries and save them
20.times{ EditedItem.push_to_queue(random_item) }
Queue.get_entries.each{|entry| assert_not_equal too_far_down, entry}
May or may not be pretty, but it gets the intention across. The hiddennow object has been pushed down in the queue too far and should no longer be returned when get_entries is called.
#this works
SearchObject.find(:all, :order => "id desc")
#this does not, unless the 20.times loop has sleep(1) or something
SearchObject.find(:all, :order => "created_at desc")
This is simplified down a bit, but it looks like the 20.times loop adds things fast enough that the order by clause on created_at cannot distinguish. My questions are, am I doing something fundamentally wrong? If not, what is the better approach to writing a test along these lines?
DigitalRoss is right. created_at
has a one second granularity.
One option is to set the created_at
when you create the objects:
old = EditItem.new(:created_at => 1.second.ago)
older = EditItem.new(:created_at => 2.seconds.ago)
Another option is to actually use stubbing to mess with the Time
class. The following would work with Rspec, but could be easily accomplished with other mocking frameworks like Mocha.
@seconds = Time.now.to_i
Time.stub!(:now).and_return{Time.at(@seconds += 5) }
This will return a time 5 seconds greater than the previous each time you call Time.now
.
I'd recommend the first approach if you can make it work, since it's more clear what you're doing and less likely to have unintended consequences.
Times related to files and records (and specifically those times in Rails) are typically kept in Unix time, or POSIX time, This format keeps the number of seconds since 1970 in an arithmetic type.
So, time for these purposes has a 1 second granularity.
Rails can't order hiddennow
vs the random items without at least a one second delay in between, and the set of 20 won't be ordered at all.
Are these answers still correct in rails 5 or 6
?
Suppose there is a legacy default scope on the User model:
#app/models/user.rb
class User
default_scope { order created_at: :desc }
end
The following rspec
test
describe 'ordering in rails' do
before(:each) do
(0..9).each_with_index do |i|
create :user, email: "#{i}@example.com"
end
end
it 'preserves order' do
puts User.pluck(:id, :created_at, :email)
expect(User.all.pluck(:email).map(&:first)).to eq %w(9 8 7 6 5 4 3 2 1 0)
end
end
yields the following output:
7602
2020-01-07 09:33:14 UTC
9@example.com
7601
2020-01-07 09:33:14 UTC
8@example.com
7600
2020-01-07 09:33:14 UTC
7@example.com
7599
2020-01-07 09:33:14 UTC
6@example.com
7598
2020-01-07 09:33:14 UTC
5@example.com
7597
2020-01-07 09:33:14 UTC
4@example.com
7596
2020-01-07 09:33:14 UTC
3@example.com
7595
2020-01-07 09:33:14 UTC
2@example.com
7594
2020-01-07 09:33:14 UTC
1@example.com
7593
2020-01-07 09:33:14 UTC
0@example.com
.
Finished in 0.30216 seconds (files took 1.34 seconds to load)
1 example, 0 failures
so despite all the models being created at the same second, there is a consistent ordering. Looking at this rails 6
merge request, it looks like that by rails 5
there is an implicit ordering on the primary key. I wonder if the id is being used to break ties in later versions of rails?
精彩评论