开发者

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?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜