DEV Community

Cover image for Idiomatic Ruby: writing beautiful code
TK
TK

Posted on

Idiomatic Ruby: writing beautiful code

Ruby is a beautiful programming language.

According to Ruby’s official web page, Ruby is a:

“dynamic, open source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.”

Ruby was created by Yukihiro Matsumoto, a Japanese software engineer. Since 2011, he has been the chief designer & software engineer for Ruby at Heroku.

Matsumoto has often said that he tries to make Ruby natural, not simple, in a way that mirrors life.

“Ruby is simple in appearance, but is very complex inside, just like our human body” — Yukihiro Matsumoto

I feel the same way about Ruby. It is a complex but very natural programming language, with a beautiful and intuitive syntax.

With more intuitive and faster code, we are able to build better software. In this post, I will show you how I express my thoughts (aka code) with Ruby, by using snippets of code.

Expressing my thoughts with array methods

Map

Use the map method to simplify your code and get what you want.

The method map returns a new array with the results of running a block once for every element in enum.

Let’s try it:

an_array.map { |element| element * element }
Enter fullscreen mode Exit fullscreen mode

Simple as that.

But when you begin coding with Ruby, it is easy to always use the each iterator.

The each iterator as shown below

user_ids = []
users.each { |user| user_ids << user.id }
Enter fullscreen mode Exit fullscreen mode

Can be simplified with map in a single beautiful line of code:

user_ids = users.map { |user| user.id }
Enter fullscreen mode Exit fullscreen mode

Or even better (and faster):

user_ids = users.map(&:id)
Enter fullscreen mode Exit fullscreen mode

Select

And when you’re used to coding with map, sometimes your code can be like this:

even_numbers = [1, 2, 3, 4, 5].map { |element| element if element.even? } # [ni, 2, nil, 4, nil]
even_numbers = even_numbers.compact # [2, 4]
Enter fullscreen mode Exit fullscreen mode

nil object as well. Use the compact method to remove all nil objects.

And ta-da, you’ve selected all the even numbers.

Mission accomplished.

Come on, we can do better than this! Did you hear about the select method from enumerable module?

[1, 2, 3, 4, 5].select { |element| element.even? }
Enter fullscreen mode Exit fullscreen mode

Just one line. Simple code. Easy to understand.

Bonus

[1, 2, 3, 4, 5].select(&:even?)
Enter fullscreen mode Exit fullscreen mode

Sample

Imagine that you need to get a random element from an array. You just started learning Ruby, so your first thought will be, “Let’s use the random method,” and that’s what happens:

[1, 2, 3][rand(3)]
Enter fullscreen mode Exit fullscreen mode

Well, we can understand the code, but I’m not sure if it is good enough. And what if we use the shuffle method?

[1, 2, 3].shuffle.first
Enter fullscreen mode Exit fullscreen mode

Hmm. I actually prefer to use shuffle over rand. But when I discovered the sample method, it made so much more sense:

[1, 2, 3].sample
Enter fullscreen mode Exit fullscreen mode

Really, really simple.

Pretty natural and intuitive. We ask a sample from an array and the method returns it. Now I’m happy.

What about you?

Expressing my thoughts with Ruby syntax

As I mentioned before, I love the way Ruby lets me code. It’s really natural for me. I’ll show parts of the beautiful Ruby syntax.

Implicit return

Any statement in Ruby returns the value of the last evaluated expression. A simple example is the **getter **method. We call a method and expect some value in return.

Let’s see:

def get_user_ids(users)
  return users.map(&:id)
end

Enter fullscreen mode Exit fullscreen mode

But as we know, Ruby always returns the last evaluated expression. Why use the return statement?

def get_user_ids(users)
  users.map(&:id)
end

Enter fullscreen mode Exit fullscreen mode

After using Ruby for 3 years, I feel great using almost every method without the return statement.

Multiple assignments

Ruby allows me to assign multiple variables at the same time. When you begin, you may be coding like this:

def values
  [1, 2, 3]
end

one   = values[0]
two   = values[1]
three = values[2]
Enter fullscreen mode Exit fullscreen mode

But why not assign multiple variables at the same time?

def values
  [1, 2, 3]
end

one, two, three = values

Enter fullscreen mode Exit fullscreen mode

Pretty awesome.

Methods that ask questions (also called predicates)

One feature that caught my attention when I was learning Ruby was the question mark (?) method, also called the **predicates **methods. It was weird to see at first, but now it makes so much sense. You can write code like this:

movie.awesome # => true
Enter fullscreen mode Exit fullscreen mode

Ok… nothing wrong with that. But let’s use the question mark:

movie.awesome? # => true
Enter fullscreen mode Exit fullscreen mode

This code is much more expressive, and I expect the method’s answer to return either a true or false value.

A method that I commonly use is any? It’s like asking an array if it has anything inside it.

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

Enter fullscreen mode Exit fullscreen mode

Interpolation

For me string interpolation is more intuitive than string concatenation. Period. Let’s see it in action.

An example of a string concatenation:

programming_language = "Ruby"
programming_language + " is a beautiful programming_language" # => "Ruby is a beautiful programming_language"
Enter fullscreen mode Exit fullscreen mode

An example of a string interpolation:

programming_language = "Ruby"
"#{programming_language} is a beautiful programming_language" # => "Ruby is a beautiful programming_language"
Enter fullscreen mode Exit fullscreen mode

I prefer string interpolation.

What do you think?

The if statement

I like to use the if statement:

def hey_ho?
  true
end

puts "let’s go" if hey_ho?
Enter fullscreen mode Exit fullscreen mode

Pretty nice to code like that.

Feels really natural.

The try method (with Rails mode on)

The try method invokes the method identified by the symbol, passing it any arguments and/or the block specified. This is similar to Ruby’s Object#send. Unlike that method, nil will be returned if the receiving object is a nil object or NilClass.

Using if and unless condition statement:

user.id unless user.nil?
Enter fullscreen mode Exit fullscreen mode

Using the **try **method:

user.try(:id)
Enter fullscreen mode Exit fullscreen mode

Since Ruby 2.3, we can use Ruby’s safe navigation operator** (&.)** instead of Rails **try **method.

user&.id
Enter fullscreen mode Exit fullscreen mode

Double pipe equals (||=) / memoization

This feature is so C-O-O-L. It’s like caching a value in a variable.

some_variable ||= 10
puts some_variable # => 10

some_variable ||= 99
puts some_variable # => 10

Enter fullscreen mode Exit fullscreen mode

You don’t need to use the if statement ever. Just use double pipe equals (||=) and it’s done.

Simple and easy.

Class static method

One way I like to write Ruby classes is to define a **static **method (class method).

GetSearchResult.call(params)
Enter fullscreen mode Exit fullscreen mode

Simple. Beautiful. Intuitive.

What happens in the background?

class GetSearchResult
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
  end
end

Enter fullscreen mode Exit fullscreen mode

The self.call method initializes an instance, and this object calls the **call **method. Interactor design pattern uses it.

Getters and setters

For the same GetSearchResult class, if we want to use the params, we can use the @params

class GetSearchResult
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
    @params # do something with @params
  end
end

Enter fullscreen mode Exit fullscreen mode

We define a setter **and **getter:

class GetSearchResult
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
    params # do something with params method here
  end

  private

  def params
    @params
  end

  def params=(parameters)
    @params = parameters
  end
end

Enter fullscreen mode Exit fullscreen mode

Or we can define attr_reader, attr_writer, or attr_accessor

class GetSearchResult
  attr_reader :param

  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
    params # do something with params method here
  end
end

Enter fullscreen mode Exit fullscreen mode

Nice.

We don’t need to define the getter and setter methods. The code just became simpler, just what we want.

Tap

Imagine you want to define a create_user method. This method will instantiate, set the parameters, and save and return the user.

Let’s do it.

def create_user(params)
  user       = User.new
  user.id    = params[:id]
  user.name  = params[:name]
  user.email = params[:email]
  # ...
  user.save
  user
end
Enter fullscreen mode Exit fullscreen mode

Simple. Nothing wrong here.

So now let’s implement it with the tap method

def create_user(params)
  User.new.tap do |user|
    user.id    = params[:id]
    user.name  = params[:name]
    user.email = params[:email]
    # ...
    user.save
  end
end
Enter fullscreen mode Exit fullscreen mode

You just need to worry about the user parameters, and the tap method will return the user object for you.

That’s it

We learned I write idiomatic Ruby by coding with

  • array methods

  • syntax

We also learned how Ruby is beautiful and intuitive, and runs even faster.

And that’s it, guys! I will be updating and including more details to my blog. The idea is to share great content, and the community helps to improve this post! ☺

I hope you guys appreciate the content and learned how to program beautiful code (and better software).

If you want a complete Ruby course, learn real-world coding skills and build projects, try One Month Ruby Bootcamp. See you there ☺

This post appeared first here on my Renaissance Developer publication.

Have fun, keep learning, and always keep coding!

My Medium, Twitter, Github & LinkedIn. ☺

Discussion (18)

Collapse
ben profile image
Ben Halpern

Fabulous post. I’m not sure I’ve read a post that expresses the wonder of Ruby so coherently.

Collapse
databasesponge profile image
MetaDave 🇪🇺

Nice.

A common problem that I have been enjoying the use of tap on is populating an array with optional elements.

Instead of:

[
  name,
  (address if address),
  (phone if phone)
].compact

... (which creates two Array objects) I have switched to ...

[].tap do |ary|
  ary << name
  ary << address if address
  ary << phone if phone
end

... or ...

[name].tap do |ary|
  ary << address if address
  ary << phone if phone
end

As is often the case, simple examples don't really do this justice. When you have a lot of complexity about what is going to be added and when, it comes into its own.

Semantically, what I particularly like is the way that tap lets you use a block to say:

  1. I will now be doing something to this object
  2. Now I am doing it
  3. It is done
Collapse
jsrn profile image
James

Lots of hot tips in here!

I like to use the rubocop gem to watch my back when I'm writing ruby. It will point out a lot of non-idiomatic bits of your code and suggest ways to improve them in line with this great article.

Collapse
arjunrajkumar profile image
Arjun Rajkumar

Awesome! I love programming with Ruby too.

Recently discovered that the .any method can be used as an iterator too.
results.any? do |result|
end

Look forward to more.

Collapse
darkwiiplayer profile image
DarkWiiPlayer • Edited on

Correct me if I'm wrong, but [1, 2, 3, 4, 5].select(&:even?) should actually be slower than explicitly writing a block, as it creates a Proc object implicitly by calling to_proc on the Symbol. There might of course be some optimization going on in the background that I don't know about. (EDIT: I totally agree that it looks neater though, and I use it a lot myself)

Collapse
richjdsmith profile image
Rich Smith

This is an awesome list of a bunch of great tips you've put together! Thank you for it!

Ruby was (and retrospectively, I'm thankful for it) the first programming language I learned. I've since picked up PHP, JavaScript and am working on Elixir, but I consider myself a proud Ruby programmer.

With the progress being made on the Ruby 3X3 goal, as well as the fact that nothing I have tried comes close to the productivity i get from working with Rails, I don't see that changing anytime soon.

I'm a Rubyist, and your list is a perfect example of why.

Collapse
sbaron24 profile image
sbaron24

Thanks for the post! In the following example under Select...

even_numbers = [1, 2, 3, 4, 5].map { |element| element if element.even? } # [ni, 2, nil, 4, nil]
even_numbers = even_numbers.compact # [2, 4]

...I noticed that after the first line, even_numbers is:

[nil, 2, nil, 4]`

Why does element if element.even? return nil when element.even? is false?

~s

Collapse
stenpittet profile image
Sten • Edited on

Thanks a lot for writing this! I've been looking for ways to use the same filter that ES6 offers but couldn't figure out the syntax in ruby. This post is now bookmarked for reference!

Collapse
brotherbain profile image
John Gaskin • Edited on

Nice article! It's worth pointing out that safe navigation is not a replacement for try, though. I've made this mistake a few times and had it bite me :D

try vs safe nav

Collapse
passthejoe profile image
Steven Rosenberg

Thank you very much for this tutorial. There are so many tips here ...

Collapse
teekay profile image
TK Author

Thanks! Glad you liked it!

Collapse
thadeu profile image
Thadeu Esteves Jr

you can as example a programatic lambda

lambda = ->(user) { user.id == 1 }
users.each(&lambda)
Collapse
rhymes profile image
rhymes

Tap is my favorite!

Great intro :)

Collapse
sudiukil profile image
Quentin Sonrel

Awesome post!

This really captures why I love Ruby so much: it's really expressive and natural when done right.

Collapse
camfilho profile image
Carlos Augusto de Medeir Filho

The Idiomatic Ruby cheat sheet

Collapse
chasestorydev profile image
chasestory

This Post Is Fantastic... Very clear, well explained.

Thank You!!

Collapse
isalevine profile image
Isa Levine

totally love this--i came back to programming after something like a 15-year hiatus, and ruby was just what i needed to fall back in love with coding (and see it for the linguistic art it truly is!)