Added "not_in?" to Rails

UPDATE: DHH yanked this patch. Ah well. Open source giveth, and open source taketh away. I sure wish we could restart this discussion and get this change made, but I’ll probably pass on fighting over this one.

Here’s the (now defunct) celebratory post…


While helping a friend today with their Rails project, I found I wanted to add a simple helper method to an object. In the existing view I found something like this:

- if @item.tag != "free" && @item.tag != "extras"

This is a code smell and I could solve it in two ways: either write a view helper or add a helper method to the Item class. In this case, I want to ask the object a question, so this makes sense to me on the object itself.

So first we change the view:

- if @item.for_sale?

And we implement on the model:

class Item < ActiveRecord::Base

  def for_sale?
    self.tag != "free" && self.tag != "extras"
  end

end

Ew. We’re checking a string attribute agaisnt two other strings. Let’s clean that up.

def for_sale?
  !["free", "extras"].include?(tag)
end

We’re basically saying “this item is for sale if the tag isn’t either ‘free’ or ‘extras’”. To shave off some cognitive load, we use Rails’ in? method and flip things around. Since we’re really talking about the tag (and not the array up front), this makes it easier to read.

def for_sale?
  !tag.in?(["free", "extras"])
end

That’s all fine and good, but that bang (!) out front is still making my mind do some gymnastics. Surely, if Rails has in? it has an opposite. But Google gave me nothing, and some other Rails friends I asked hadn’t heard of such a thing.

Turns out, Rails doesn’t have an opposite for in?. Ruby has include? and Rails implements exclude?, which is nice. But Rails implements in? without an opposite.

Putting aside the fact that I think these methods (exclude? and in?) should be added to Ruby core, I would love to see Rails go that extra step and have a not_in? method.

… and now it does!

As of this commit, Rails now has a not_in? method. It works like this:

Replace this:

[1,2].exclude?(user_id)

…into this:

user_id.not_in?([1,2])

So in our previous example, we can now lead with the object in question. So this:

def for_sale?
  !tag.in?(["free", "extras"])
end

… turns into this:

def for_sale?
  tag.not_in?(["free", "extras"])
end

Nice and clean! Enjoy!

Hi there, I'm Jon.

Writer. Musician. Adventurer. Nerd.

Purveyor of GIFs and dad jokes.