ActiveRecord scopes and lambdas
Had another annoying issue with posts on this blog not appearing immediately after being published.
class Post < ActiveRecord::Base
scope :published, where(['published_at IS NOT NULL AND published_at < ?', Time.now])
end
</pre></code>
So any Post with a published_at in the past should be found in Post.published.all for example. The post is missing. I thought this was an issue in *production* so I enabled logging and redeployed.
<pre><code>
Robl::Application.configure do
.....
# Set to :debug to see everything in the log.
# config.log_level = :info
config.log_level = :debug
.....
Looking at the log it seems fine initially, but then I realise that the timestamp for the published_at date is the same ‘2014-09-29 09:40:59’ seconds after the the page is reloaded.
D, [2014-10-02T21:51:22.979289 #4499] DEBUG -- : (0.3ms) SELECT COUNT(*) FROM `posts` WHERE (published_at IS NOT NULL AND published_at < '2014-09-29 09:40:59')
D, [2014-10-02T21:52:56.305813 #4499] DEBUG -- : Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE (published_at IS NOT NULL AND published_at < '2014-09-29 09:40:59') ORDER BY published_at DESC LIMIT 25 OFFSET 0
...
D, [2014-10-02T21:52:56.384903 #4499] DEBUG -- : (0.2ms) SELECT COUNT(*) FROM `posts` WHERE (published_at IS NOT NULL AND published_at < '2014-09-29 09:40:59')
...
Since Time.now is apparently standing still a bug in the model scope definition is incorrect. The where doesn’t evaluate Time.now every time the scope is called it evaluates it when the class is loaded. The ActiveRecord Query needs to be placed inside a lambda in order that its contents are evaluated when the scope is called.
class Post < ActiveRecord::Base
scope :published, -> { where(['published_at IS NOT NULL AND published_at < ?', Time.now]) }
end
And success. Time to redeploy.