loading...

From JavaScript to Ruby: A few of my favourite features

harri_etty profile image Harriet ・6 min read

Ruby

Coming from JavaScript to Ruby, I was excited to learn the language that promised to be "friendly to developers" and "designed for developer happiness". I discovered a language which, like JS, is dynamic, object oriented and general purpose. Like JS, it also offers lots of ways to do the same thing, enabling the developer to have a fair amount of stylistic autonomy (if you like that kind of thing).

Ruby has a fairly low learning curve, since it seems to have been designed with lots of sensibly-named, consistent and easy to use methods, and it also has no concept of asynchronicity out of the box, making code easier to reason about than JavaScript.

Here are a few things I've noticed which I think are pretty neat about Ruby, in comparison to JavaScript!

Being able to check memory location

Unlike in JavaScript, Ruby lets you inspect the location in memory of a value with the object_id method:

For example, if we look at the object ID of 2 hashes:

a = {name: 'Harriet'}
b = {name: 'Heather'}
puts a.object_id # 6478367
puts b.object_id # 6471222

Those numbers are memory addresses. The actual addresses aren't that useful but it might help to see when you're dealing with two references to the same location in memory, or references to separate locations in memory.

I've never used this in a practical sense, but it was helpful when I wanted to explore the difference in how Strings and Symbols work in Ruby (see next section). There's no way in JavaScript to inspect where items live in memory, which has been annoying when I've been trying to demonstrate how JavaScript passes objects by reference, and primitives by value.

Symbols

In JavaScript, you have a few of ways of creating a string, the first two here being the most common:

let a = 'Hello world'
let b = "Hello world" // Functionally no different to using single quotes
let b = new String('Hello world') // Creates a String object
let c = `Hello world` // ES6 String Literal

In Ruby, there are also a few options:

a = 'Hello world'
b = "Hello world" # Double quotes allow for string interpolation & escape characters
c = String.new('Hello world')
d = String('Hello world')

In Ruby, by default, all Strings are types of String Objects, and as Objects they occupy different places in memory, even if the contents of two or more strings is the same. Potentially a bit wasteful, storing the exact same information twice over!

You can check this by looking at the object ID of 2 identical strings:

a = 'Hello world'
b = 'Hello world'
puts a.object_id # 6478367
puts b.object_id # 6471222

That’s where Symbols come in. A Symbol is created with a : at the beginning and means that any time the Symbol is used, it will reference the same value.

a = :hello
b = :hello
puts a.object_id # 1111708
puts b.object_id # 1111708

This works great for single words, but you can even turn a longer string into a Symbol and increase efficiency with the .to_sym method:

a = 'Hello world'.to_sym
b = 'Hello world'.to_sym
puts a.object_id # 92880
puts b.object_id # 92880

I use symbols over strings wherever I can, not just when I know a value will be used again in a program! Why not, when it’s easy to do and there’s nothing to lose?

Simple loops ➰

In JavaScript, sometimes you just want to loop a set number of times. You don't care about the start point or the end point, as long as your code executes n times. However, you're forced to explicitly construct the conditions for iteration yourself, starting with i = 0 and defining when you want the loop to end:

for (let i = 0; i < 10; i++) {
 // do stuff
}

In Ruby, you can simply do:

10.times do
  # do stuff
end

It's a simple, less imperative way of executing code a set number of times.

Functions are stricter about arguments

I like that in Ruby, you get an error if you give a function the wrong number of arguments. It just speeds up the process of debugging your code.

def greet(name)
  puts "Hello, #{name}"
end

greet()

# wrong number of arguments (given 0, expected 1)

You can also name your parameters, and if they're not passed, or you pass something unexpected, you'll get an error:

def greet(name:, city:)
  puts "Hello, #{name} from #{city}"
end

greet(name: 'Harriet', city: 'Manchester')

greet(name: 'Harriet') # missing keyword: city

greet(name: 'Harriet', city: 'Mancheseter', age: 27) # unknown keyword: age

No function call parentheses

In JavaScript, you must use parentheses when calling a function, for example add(1, 2).

In Ruby, parentheses are generally optional, which can sometimes lead to Ruby that looks very natural language-y and easy to read. For example, a testing library can provide a to method which, when used without parentheses, reads like this:

expect(result).to be_null

Although it can get a bit confusing if you've got multiple arguments. For example, is 5 the second argument to bar, or the second argument to foo? Without brackets it's not clear:

def foo(a, b)
  puts "in foo #{a}, #{b}"
end

def bar(a)
 12 + a
end

foo bar 55, 5 # wrong number of arguments (given 2, expected 1)

foo bar(55), 5 # Correct - 5 is the second argument to foo

Calling a function without parentheses also means we can do something like this:

def greet(name = 'Harriet')
  puts "Hello, #{name}"
end

greet

See how just referring to the greet method actually invokes it with no arguments? This is how Ruby implements getter methods on objects. When you call person.name for example, name is actually a method on that object, which retrieves the name instance varaible. It's not simply an object property like in JavaScript.

One effect of parentheses-less method calls means we can't pass methods around as values, like we can in JavaScript. In JavaScript, we can do this:

function greet(name) {
  console.log(`Hello, ${name}`);
}

const welcomer = greet;

welcomer('Harriet');

But in Ruby, trying to pass a reference to the method actually invokes it! So we end up with:

def greet(name = 'Harriet')
  puts "Hello, #{name}"
end

welcome = greet # This line actually executes the greet function

welcome "Paula" # undefined method `welcome' for main:Object

Just one way to create Classes

In JavaScript there is not really a concept of true classes, at least not in the way people from truly Object Oriented languages would expect them to be. Instead we have a the prototype chain, and at least 4 different ways of creating objects with shared methods and behaviour. This is super confusing, so I really like that Ruby just offers the one way to do it!

Creating class methods, class variables, instance methods and instance variables is much more straightforward in Ruby:

class Person
  attr_reader :name, :title

  # Class variables
  @@legs = 2
  @@arms = 2
  @@diet = 'omnivore'

  def initialize(name, title)
    # @name and @title are instance variables
    @name = name
    @title = title
  end

  # Instance method
  def greet
    puts "Good day, #{title} #{name}!"
  end

  # Class method
  def self.describe
    puts "A person is a #{@@legs}-legged, #{@@arms}-armed #{@@diet}"
  end
end

jerry = Person.new('Jerry Jones', 'Mr')

jerry.greet

Person.describe

Implicit returns

In Ruby, the return statement is optional, or can be used to return early from a function. If you omit it, the function will return the last evaluation.

def double(nums)
  nums.map{ |n| n * 2 }
end

Metaprogramming

This one's quite a big a topic and I don't know it that well so I'm only going to touch on it briefly. Metaprogramming means a program being able to modify itself at runtime, based on the state of the program at that time.

Rails uses Metaprogramming to allow us to do something like this:

Book.find_by_ISBN("19742084746")

You defined the Book class when you set up your Models, but nowhere did you defined the find_by_ISBN method. Defining a find_by_x for all your columns would be really tiresome; it's no wonder the Rails framework doesn't want to make you go to all that work. But Rails itself didn't add that method for you, either. How would Rails magically know what your Book instances needed a find_by_ISBN method?

Instead, when Rails sees you trying to use the find_by_ISBN method it will extract the ISBN part and attempt to match it to a column in the database, and if successful, will execute some code to find your item based on the ISBN column, responding as though find_by_ISBN were an actual method that had been defined on Book instances.

This is just one example of what Metaprogramming can do!

Personally I think it's pretty cool, and once you know it exists, you begin seeing it "in the wild" all over the place in Ruby. It's the foundation stone for being able to create DSLs (Domain Specific Languages) like Rails and makes Ruby extremely flexible.

Posted on by:

harri_etty profile

Harriet

@harri_etty

Software engineer, runner, yogi, language learner

Discussion

markdown guide
 

It's not really a memory location. It's just an object id. Yes it's useful to see if two objects are really the same exact object, which is kind of similar to a memory location, but for example, there is a object id of 1.

ObjectSpace._id2ref(1).object_id
=> 1

That's hardly a memory location. :)

ObjectSpace can do some interesting things, none of which should probably be used in normal programming.

 

One effect of parentheses-less method calls means we can't pass methods around as values, like we can in JavaScript.

We can. Use


 

Can't see what you wrote :(