any? != ! empty?

Jared Carroll

A common Ruby/Rails idiom used in views looks like the following:

<% if @posts.empty? -%>
  There are no posts.
<% else -%>
  <ul>
    <%= render :partial => 'post', :collection => @posts -%>
  </ul>
<% end -%>

Say that was from /posts or PostsController#index.

Alternatively, I’ve seen this:

<% if @posts.any? -%>
  <ul>
    <%= render :partial => 'post', :collection => @posts -%>
  </ul>
<% else -%>
  There are no posts.
<% end -%>

Testing for the positive first instead of the negative first.

The following is also valid, but its not legal in my book because it’s trash:

<% unless @posts.empty? -%>
  <ul>
    <%= render :partial => 'post', :collection => @posts -%>
  </ul>
<% else -%>
  There are no posts.
<% end -%>

I bet that took a couple takes before you got that. ‘unless … else’ is a terrible Ruby construct.

Sorry got sidetracked on my ‘unless … else’ hate.

Take a look at the following Ruby:

[1, 2].any? => true
[nil, nil].any? => false

[1, 2].empty? => false
[nil, nil].empty? => false

I never liked using #any? without a block, it felt/feels too strange. Now if you don’t pass #any? a block you get a default block, which looks like this:

[1, 2].any? {|each| each}

Now #any? is a method that asks is there anything in this collection that’s true?. In Ruby, the existence of an object is considered true; so calling #any? without a block is saying is there something that’s not nil or false in this collection?.

So the view code from above:

<% if @posts.any? -%>
  <ul>
    <%= render :partial => 'post', :collection => @posts -%>
  </ul>
<% else -%>
  There are no posts.
<% end -%>

Isn’t it really saying is there anything in this posts collection? Because that collection could contain nil. And nil is an object, its an instance of NilClass; so the collection actually does have something in it. Using #any? is this context, basically as way of saying ‘not empty’, is incorrect because it’s relying on Ruby’s ugly feature of ‘object existence implies truth’ to test if there’s anything in a collection.

The correct code, when testing the positive first, should be:

<% if ! @posts.empty? -%>
  <ul>
    <%= render :partial => 'post', :collection => @posts -%>
  </ul>
<% else -%>
  There are no posts.
<% end -%>