ES6 for Rails developers
If your JavaScript knowledge stopped at jQuery, modern JavaScript (ES6) will feel surprisingly close to Ruby. A tour of the features that matter — let/const, arrow functions, destructuring, classes, modules, and promises — through Rubyist eyes.
A lot of Rails developers have a slightly uneasy relationship with JavaScript: they learned just enough jQuery years ago to get by, and have watched the front end transform from a safe distance. The good news is that modern JavaScript — ES6, properly ES2015, now widely usable via Babel — closed most of the gap that made the language feel hostile, and a great deal of it will feel pleasantly familiar to a Rubyist. Here is a tour of the features that matter, framed for someone who thinks in Ruby.
let and const: block scope at last
The first fix addresses JavaScript’s most notorious wart: var is function-scoped and
hoisted, which produces genuinely surprising bugs. ES6 replaces it with let
(block-scoped, reassignable) and const (block-scoped, not reassignable):
const TAX_RATE = 0.2; // cannot be reassigned
let total = 0; // block-scoped, can change
for (let i = 0; i < items.length; i++) { /* i is scoped to the loop */ }
The rule that has emerged, and that we follow: use const by default, let when you must
reassign, and var never. A Rubyist will find this comfortable — const is your
near-default just as you reach for immutable-ish values in Ruby, and block scoping behaves
the way your intuition from if/do blocks expects.
Arrow functions: shorter, and they fix this
Arrow functions are more concise and, crucially, solve JavaScript’s infamous this problem.
A regular function rebinds this depending on how it is called — the source of countless
“why is this undefined?” bugs in callbacks. An arrow function inherits this from its
surrounding scope:
// concise, and `this` is the surrounding object, not rebound
const doubled = numbers.map(n => n * 2);
button.addEventListener("click", () => {
this.handleClick(); // `this` is what you expect, not the button
});
To a Rubyist, numbers.map(n => n * 2) reads almost exactly like numbers.map { |n| n * 2 }
— the mental model of “pass a small function to a collection method” transfers directly.
Template literals: string interpolation
JavaScript finally has string interpolation, and it works just like Ruby’s, with backticks
and ${} instead of double-quotes and #{}:
const greeting = `Hello, ${user.name}! You have ${cart.size} items.`;
// multi-line works too, no more "line one\n" + "line two"
After years of "Hello, " + user.name + "!" string concatenation, this alone makes the
language pleasanter. It is a direct analogue of "Hello, #{user.name}!".
Destructuring: pulling values apart
Destructuring lets you unpack arrays and objects into variables in one expression — similar to Ruby’s multiple assignment, but extended to objects (hashes):
const [first, second] = items; // like Ruby's a, b = items
const { name, email } = user; // pull keys out of an object
const { id, ...rest } = params; // id, plus everything else in `rest`
function greet({ name, locale = "en" }) { // destructure + default in the signature
// ...
}
The object destructuring in function parameters is especially useful — it is the JavaScript equivalent of Ruby’s keyword arguments, giving you named, self-documenting parameters with defaults.
Classes and modules: structure
ES6 adds class syntax — syntactic sugar over JavaScript’s prototype system, but it reads
much like a Ruby class and makes the common case clear:
class ShoppingCart {
constructor(items = []) {
this.items = items;
}
get total() {
return this.items.reduce((sum, i) => sum + i.price, 0);
}
add(item) { this.items.push(item); }
}
And modules give JavaScript real imports and exports — explicit dependencies and scoping, the foundation that makes Webpack’s dependency graph possible:
// cart.js
export class ShoppingCart { /* ... */ }
export const FREE_SHIPPING = 5000;
// app.js
import { ShoppingCart, FREE_SHIPPING } from "./cart";
This is the structure that was missing for so long. A Rubyist will recognise the instinct: each file is a unit with a clear public interface, like a well-factored Ruby class or module, instead of everything sharing one global namespace.
Promises: taming asynchronous code
The hardest conceptual leap for a server-side developer is JavaScript’s asynchrony. Network calls do not block; they complete later, and the old way of handling that — nested callbacks — produced the infamous “pyramid of doom”. Promises flatten it into a readable chain:
fetch(`/api/products/${id}`)
.then(response => response.json())
.then(product => render(product))
.catch(error => showError(error));
A Promise represents “a value that will arrive later”, with .then for success and .catch
for failure. This is the part with no direct Ruby analogue — Ruby code mostly runs top to
bottom and blocks on I/O — so it is worth spending real time on. (Even cleaner async/await
syntax is coming, which makes asynchronous code read like synchronous code; it is worth
knowing it is on the way.)
The smaller wins
Rounding it out: default parameters (function f(x = 1)), rest and spread
(...args to gather, [...a, ...b] to expand — like Ruby’s splat), and Array/Object
helpers that make collection work feel more like Ruby’s Enumerable. Each removes a small
papercut a Rubyist used to trip over.
Verdict
If your JavaScript stalled in the jQuery era, ES6 is the moment the language becomes pleasant
to write — and much of it maps cleanly onto instincts you already have from Ruby. const by
default, arrow functions for concise callbacks with sane this, template literals for
interpolation, destructuring for named parameters, classes and modules for structure. The one
genuinely new mental model is asynchrony — promises (and soon async/await) — which has no
Ruby equivalent and deserves real study. Learn these, lean on Babel (or Webpacker) to run
them everywhere, and the front end stops feeling like a foreign country and starts feeling
like a language you can be productive in. For a Rails developer in 2017, that fluency is no
longer optional — and happily, it is closer to home than you think.