qfh2ua
Last Updated: March 03, 2016
·
10.33K
· dtao
3c9c19ca551799cf691fddaae5056e55

Ruby tricks to make your code more fun and less readable

Ruby is a language that Yukihiro Matsumoto designed to be fun, to optimize developer happiness. I appreciate this more and more whenever I discover some quirky feature hidden in the nooks and crannies of the language. This is going to be a quick survey of some of those interesting, "fun" features.

Hopefully the tongue-in-cheek title makes it clear that I'm not recommending any of this for real, production code. That said, if you like any of these tricks and your teammates do too, I don't think they're necessarily harmful. Knock yourselves out, I say!

Using & to create procs

Some Ruby devs know this, some don't. Say I have an array of strings and I want to capitalize them.

strs = ['foo', 'bar', 'baz']
caps = strs.map { |str| str.upcase }

Pretty familiar. I can make this a bit more concise using the & operator:

caps = strs.map(&:upcase)

The & takes any object and calls to_proc on it. And for the Symbol class, the to_proc method gives you a proc that, given some object, will send itself (the symbol) as a message to that object. So the above is essentially the same as this:

caps = strs.map { |str| str.send(:upcase) }

Now, what if instead of calling an instance method, you want to pass an object as a parameter to a method?

def piglatinify(word)
  word[1..-1] + word[0] + 'ay'
end

# Can we do this with & instead?
piglatinified = strs.map { |str| piglatinify(str) }

This can be done, as it turns out. First get a Method object using method, then stick a & in front of it to get a proc:

piglatinified = strs.map(&method(:piglatinify))

More fun with blocks and lambdas

Which brings me to another little trick. You have probably used blocks like this:

# Define a method that *takes* a block
def apply_block(arg, &block)
  yield arg
end

# Call the method *with* a block
apply_block('foo') do |str|
  str.upcase
end

Meanwhile, I'm sure you've probably used lambdas before:

upcase_lambda = lambda { |str| str.upcase }
upcase_lambda.call('foo')

Did you know you can easily use a lambda as a block?

apply_block('foo', &upcase_lambda)

You can also use the [] method to invoke a lambda (rather than using call):

upcase_lambda['foo']

Also, did you know that as of Ruby 1.9 there's an alternate syntax for defining lambdas? Frankly, it's pretty weird and I tend to avoid it. But it exists, and it looks like this:

upcase_lambda = -> s { s.upcase }

What's this? You already knew all that? Fine. I hope you realize there are no prizes here.

Playing with heredocs

You've probably used this feature of Ruby once or twice:

heredoc_haiku = <<-EOT
  This is a heredoc
  It provides a nice syntax
  For multiline strings
EOT

Now, there is a dilemma here: the syntax is convenient, but it is takes white space literally. So if you put indentation in the string to remain consistent with your code, that indentation will be preserved. This is why you see ugly code like this sometimes:

module Foo
  class Bar
    def print_haiku
      puts <<-EOT
This is a heredoc
It provides a nice syntax
For multiline strings
      EOT
    end
  end
end

As it turns out, you can manipulate a heredoc string from the point where it's declared:

heredoc_haiku = <<-EOT.gsub(/^\s+/, '') # Eliminate leading space
  This is a heredoc
  It provides a nice syntax
  For multiline strings
EOT

Actually, "manipulate" is too strong a word. You can call any method on a heredoc string. For example, here we use heredoc syntax to get an array of sentences, each comprising an array of words:

sentences = <<-EOT.lines.map(&:split)
  The quick brown fox jumped over the lazy dogs.
  The rain in Spain stays mainly in the plain.
  All work and no play makes Jack a dull boy.
EOT

Playing with *, the splat operator

The splat operator (*) can be quite useful. You've probably seen it used to define a method that accepts a variable number of arguments:

def count(*args)
  puts "Received #{args.length} arguments."
end

count(1, 2, 'foo', nil, false) # => Received 5 arguments.

A helpful way to think of splatting is that it takes a value or an array of values, and evaluates them as if they'd been expressed literally in your code.

Here's an example:

def sum(x, y, z)
  x + y + z
end

values = [1, 2, 3]

# This will be evaluated as if you had written the code:
# sum(1, 2, 3)
sum(*values)

And it works the other way, as we've already seen:

def sum(*values)
  values.inject(0) { |x, y| x + y }
end

# This will be evaluated so that the 'values' argument
# gets passed in as [1, 2, 3].
sum(1, 2, 3)

There's a lot more you can do with splats. For example, say you have an array and you want to assign the first element to one variable, and the rest to another:

arr = [1, 2, 3, 4, 5]
first, *rest = arr

# Now first is 1, and rest is [2, 3, 4, 5]

You can also use * on a single value, which will evaluate to the value if it isn't nil or otherwise will act as if it weren't there at all. Here's what I mean:

def pack(x, y, z)
  [*x, *y, *z]
end

The above method will create an array of the value(s) from x, y, and z that are not nil. In other words it's functionally the same as:

def pack(x, y, z)
  [x, y, z].compact
end

This opens up another scenario. Let's implement a to_hash method on Enumerable, where we'll accept a block that takes each element and can either return a key/value pair, or just a key (in which case the value will be the original element).

module Enumerable
  def to_hash(&block)
    hash = {}
    self.each do |e|

      # If (yield e) returns TWO values, then
      # val will take on the second one;
      # otherwise, it will be assigned the value of e.
      key, val = [*(yield e), e]

      hash[key] = val
    end
    hash
  end
end

Yeah, splats are fun. I've wasted a lot of time playing with them for no good reason.

Chaining enumerators

What happens if you call map on an array and don't pass a block? You get an Enumerator object.

e = [1, 2, 3].map
e.class # => Enumerator

One thing that's helpful to know here is that you can chain these together. For example, suppose you want to map with some logic that needs an element's index. You could always do it the sloppy way:

i = 0
results = array.map do |e|
  # Do something w/ e and i
  i += 1
end

But you could chain this atop each_with_index instead:

results = array.each_with_index.map do |e, i|
  # Do something w/ e and i
end

Or you might like to group a sequence with each_slice:

pairs = [1, 2, 3, 4, 5, 6].each_slice(2).map(&:reverse)
# => [[2, 1], [4, 3], [6, 5]]

Abrupt conclusion

Well, I think that's enough for now. Maybe I'll write another protip with even more quirky Ruby features down the road.

If you learned even one new thing from this protip, I'm glad to have taught you something! Even if it isn't that useful. On the other hand, if you already knew all of this and I just wasted ~5 minutes of your time, my sincere apologies. But it was all free, so... you get what you pay for, eh?

Actually, here's an idea: if you didn't learn anything from this protip, how about you teach me something in a comment? That would be pretty sweet.

Say Thanks
Respond

14 Responses
Add your response

10281
B51e7cdcc7a02568f78c50c5eeee2439

I knew about to_proc, but I did not realize you could pass it a Method like that. Nice tips!

over 1 year ago ·
10295
54588 555132027835563 687647376 o

Nice Job. Great Tips.

over 1 year ago ·
10331
Me

In Ruby, "splatted" argument may be in the middle, so function like this:

def first_middle_last(first, *middle, last)
  p first, middle, last
end

is fully correct and functional:

first_middle_last(1, 2) #=> 1, [], 2
first_middle_last(1, 2, 3, 4) #=> 1,  [2,3], 4
over 1 year ago ·
10346
4693d7cfa88635d430c0de9a92f8dd84

Great tips!

To add more fun:
In the first example, you could substitute the array of words using '%' notation, like this:

strs = %w(foo bar baz)
over 1 year ago ·
10354
3c9c19ca551799cf691fddaae5056e55

@hauleth Sweet! That is indeed a nice trick that I didn't know about. +1 from me.

over 1 year ago ·
10355
3c9c19ca551799cf691fddaae5056e55

@jonahoffline Ah yes, good ol' %w. That is a nice feature. Did you know there is also %r for regexes and in Ruby 2 there's %s (I believe) for an array of symbols?

over 1 year ago ·
10356
Me

@dtao for symbols is %i. Also in Ruby 2.0 there is ** (splat splat) operator for named arguments, so:

def foo(**args)
  p args
end

foo(a: 10, b: 20) #=> {:a => 10, :b => 20}
over 1 year ago ·
10358
3c9c19ca551799cf691fddaae5056e55

@hauleth Oh right, %i (I'm on my phone; probably should have checked before posting). And I didn't know about double splat either! I'll have to play around with that.

You've now taught me two new things in the comments! My hat's off to you.

over 1 year ago ·
10359
Me

@dtao to thank me just keep working on Lazy.js. IMHO it is great library and is much better than Underscore, so keep going.

over 1 year ago ·
10366
4693d7cfa88635d430c0de9a92f8dd84

Yeah, they rock! If anyone needs the complete list for using % notation: http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals

over 1 year ago ·
10384
0 fquk8jnlrmff1hnqs9a 8yklmgkq15kqdzzf8yqaqykn23pn  yyao8hbzrmkc54acmm7jowe25q

Some pretty nice tips on there, particularly the heredoc one, I recently wrote a heredoc with a gsub after the EOT terminator (to remove white-space before I made an API call).

I made a gist of snippets I like in Ruby shortly after reading Ruby Best Practices, don't get me wrong, I don't claim some of this to be best practices (the last portion on Regex).

https://gist.github.com/Andy-Holland/5553433

over 1 year ago ·
10413
Cs ava

Really enjoyed this post. Thanks Dan.

over 1 year ago ·
10419
6b67518d6700874714698702879014a2

My 2 cents:

case you
when -> y {y.happy?}  then y.clap_hands #stolen from Avdi (?)
when PersonAnalyzer.method :hulk?     then y.destroy!
end
  • Shameless self-promotion: weekend experiment of mine. which allows something like:
puts mike.tell{"Call me #{name}. First thing you should know - don`t do business with #{cook.name}"}

or

gus = cast.find &which{ surname =~ /esposito/i }

but at the same time - still restricts protected and private methods:
https://github.com/idrozd/red_bikini

over 1 year ago ·
10431
E90b0742a0a78f583355470561d63197

I wrote a protip a while ago that expands on the splat idea for list processing constructs in ruby: https://coderwall.com/p/qgis3g

over 1 year ago ·