“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay...”

Rob Lacey (contact@robl.me)
Senior Software Engineer, Brighton, UK

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.