giant robots smashing into other giant robots

We are thoughtbot, a web design and development agency in Boston, MA.

Comments (View)

any? != ! empty?

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


  <% if @posts.empty? -%>
    <p>There are no posts.</p>
  <% 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 -%>
    <p>There are no posts.</p>
  <% 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 -%>
    <p>There are no posts.</p>
  <% 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 existance 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 -%>
    <p>There are no posts.</p>
  <% end -%>

Isnt’t 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 -%>
    <p>There are no posts.</p>
  <% end -%>