The Downside of Cleverness

Ruby lets you do all sorts of clever, fancy tricks. This cleverness is a big part of what makes Ruby so elegant, but it also can be downright dangerous in the wrong hands. To illustrate this, we’ll look at the kind of trouble you can get in if you aren’t careful.

The Evils of eval()

Throughout this book, we’ve dynamically evaluated code blocks all over the place. However, what you have not seen much of is the use of eval(), class_eval(), or even instance_eval() with a string. Some might wonder why this is, because eval() can be so useful! For example, imagine that you are exposing a way for users to filter through some data. You would like to be able to support an interface like this:

user1 = User.new("Gregory Brown", balance: 2500)
user2 = User.new("Arthur Brown", balance: 3300)
user3 = User.new("Steven Brown", balance: 3200)

f = Filter.new([user1, user2, user3])
f.search("balance > 3000") #=> [user2, user3]

Armed with instance_eval, this task is so easy that you barely bat an eye as you type out the following code:

class User
  def initialize(name, options)
    @name    = name
    @balance = options[:balance]
  end

  attr_reader :name, :balance
end

class Filter
  def initialize(enum)
    @collection = enum
  end

  def search(query)
    @collection.select { |e| e.instance_eval(query) }
  end
end

Running the earlier example, you see that this code works great, exactly as expected. But unfortunately, trouble strikes when you see queries like this:

>> f.search("@balance = 0") => [#<User:0x40caa4 ...

Get Ruby Best Practices now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.