Functions

The Functions filter provides a large number of convenience methods Rubyists are familar with. Statements such as "252.3".to_i transform into parseInt("252.3"), or [1,3,5].yield_self { |arr| arr[1] } into (arr => arr[1])([1, 3, 5]). Generally you will want to include this filter in your configuration unless you have specific reason not to.

List of Transformations

  • .abs Math.abs()
  • .all? .every (with block) or .every(Boolean) (without block)
  • .any? .some (with block) or .some(Boolean) (without block)
  • .ceil Math.ceil()
  • .chars Array.from()
  • .chr fromCharCode
  • .clear .length = 0
  • .compact .filter(x => x != null)
  • .compact! .splice(0, a.length, ...a.filter(x => x != null)) (mutating)
  • debugger debugger (JS debugger statement)
  • .define_method klass.prototype.meth = function ...
  • .delete delete target[arg]
  • .downcase .toLowerCase
  • .each .forEach
  • .each_key for (i in ...) {}
  • .each_pair for (let key in item) {let value = item[key]; ...}
  • .each_value .forEach
  • .each_with_index .forEach
  • .end_with? .slice(-arg.length) == arg
  • .empty? .length == 0
  • .find_all {} .filter() (alias for .select)
  • .find_index findIndex
  • .first [0]
  • .first(n) .slice(0, n)
  • .flat_map {} .flatMap()
  • .floor Math.floor()
  • .freeze Object.freeze()
  • .group_by {} Object.groupBy() (ES2024+) or .reduce() fallback
  • .group_by {|k,v| ...} destructuring support ([k, v]) => ...
  • .gsub replace(//g)
  • .has_key? key in hash
  • .include? .indexOf() != -1
  • .index indexOf (when using arg) or findIndex (when using block)
  • .inspect JSON.stringify()
  • .join .join('') (Ruby defaults to "", JS to ",")
  • .key? key in hash
  • .keys() Object.keys()
  • .last [*.length-1]
  • .last(n) .slice(*.length-1, *.length)
  • .lstrip .replace(/^\s+/, "")
  • .max Math.max.apply(Math)
  • .max_by {} .reduce()
  • .member? key in hash
  • .merge Object.assign({}, ...)
  • .merge! Object.assign()
  • .method_defined? klass.prototype.hasOwnProperty(meth) or meth in klass.prototype
  • .min Math.min.apply(Math)
  • .min_by {} .reduce()
  • .negative? < 0
  • .none? !.some (with block) or !.some(Boolean) (without block)
  • [-n] = x [*.length-n] = x for literal negative index assignment
  • .new(size,default) == .new(size).fill(default)
  • .nil? == null
  • .ord charCodeAt(0)
  • .positive? > 0
  • puts console.log
  • rand Math.random
  • .reject {} .filter(x => !(...)) (negated condition)
  • .reject(&:method) .filter(item => !item.method()) (symbol-to-proc)
  • .replace .length = 0; ...push.apply(*)
  • .respond_to? right in left
  • .rindex .lastIndexOf
  • .round Math.round()
  • .rstrip .replace(/s+$/, "")
  • .scan .match(//g)
  • .select {} .filter()
  • .send dynamic method dispatch obj.send(:foo, x) becomes obj.foo(x) or obj[method](x)
  • .sort_by {} .toSorted() (ES2023+) or .slice().sort() fallback
  • .sum .reduce((a, b) => a + b, 0)
  • .reduce(:+) .reduce((a, b) => a + b) (symbol-to-proc for operators)
  • .reduce(:merge) .reduce((a, b) => ({...a, ...b})) (hash merge)
  • .times for (let i = 0; i < n; i++)
  • .start_with? .startsWith(arg)
  • .upto(lim) for (let i=num; i<=lim; i+=1)
  • .downto(lim) for (let i=num; i>=lim; i-=1)
  • .step(lim, n).each for (let i=num; i<=lim; i+=n)
  • .step(lim, -n).each for (let i=num; i>=lim; i-=n)
  • (0..a).to_a [...Array(a+1).keys()]
  • (b..a).to_a Array.from({length: (a-b+1)}, (_, idx) => idx+b)
  • (b...a).to_a Array.from({length: (a-b)}, (_, idx) => idx+b)
  • .strip .trim
  • .sub .replace
  • .tap {|n| n} (n => {n; return n})(...)
  • .to_f parseFloat
  • .to_i parseInt
  • .to_s .toString
  • .to_sym (removed - symbols are strings in JS)
  • .to_json JSON.stringify(obj)
  • JSON.generate(x) JSON.stringify(x)
  • JSON.pretty_generate(x) JSON.stringify(x, null, 2)
  • JSON.parse(x) JSON.parse(x)
  • typeof(x) typeof x (JS type checking operator)
  • .upcase .toUpperCase
  • .yield_self {|n| n} (n => n)(...)
  • .zero? === 0
  • [-n] [*.length-n] for literal values of n
  • [n...m] .slice(n,m)
  • [n..m] .slice(n,m+1)
  • [start, length] .slice(start, start+length) (Ruby 2-arg slice)
  • [n..m] = v .splice(n, m-n+1, ...v)
  • .slice!(n..m) .splice(n, m-n+1)
  • [/r/, n] .match(/r/)[n]
  • [/r/, n]= .replace(/r/, ...)
  • (1..2).each {|i| ...} for (let i=1; i<=2; i+=1)
  • "string" * length "string".repeat(length)
  • [element] * n Array(n).fill(element)
  • [a, b] * n Array.from({length: n}, () => [a, b]).flat()
  • array + [elements] array.concat([elements])
  • @foo.call(args) this._foo(args)
  • @@foo.call(args) this.constructor._foo(args)
  • Array(x) Array.from(x)
  • delete x delete x (note lack of parenthesis)

Regular Functions with Function.new

By default, Ruby blocks become JavaScript arrow functions (=>), which have lexical this binding. When you need a regular function with dynamic this binding (e.g., for DOM event handlers, method composition, or prototype methods), use Function.new:

fn = Function.new { |x| x * 2 }
# => let fn = function(x) {x * 2}

callback = Function.new { handle_click(this) }
# => let callback = function() {handle_click(this)}

Compare with regular procs (arrow functions):

fn = proc { |x| x * 2 }
# => let fn = x => x * 2

When to use Function.new:

  • jQuery/DOM event handlers where this should refer to the element
  • Method composition with dynamic super calls
  • Situations where this must be determined at call time, not definition time

Alternative: You can also use the Pragma filter with # Pragma: function to force regular function syntax for any block.

Additional Features

  • .sub! and .gsub! become equivalent x = x.replace statements
  • .map!, .reverse!, and .select! become equivalent .splice(0, .length, *.method()) statements
  • setInterval and setTimeout allow block to be treated as the first parameter on the call
  • for the following methods, if the block consists entirely of a simple expression (or ends with one), a return is added prior to the expression: sub, gsub, any?, all?, map, find, find_index.
  • New classes subclassed off of Exception will become subclassed off of Error instead; and default constructors will be provided
  • loop do...end will be replaced with while (true) {...}
  • n.times do...end and n.times { |i| ... } will be replaced with for loops
  • raise Exception.new(...) will be replaced with throw new Error(...)
  • block_given? will check for the presence of optional argument _implicitBlockYield which is a function made accessible through the use of yield in a method body.
  • alias_method works both inside of a class definition as well as called directly on a class name (e.g. MyClass.alias_method)
  • define_method and method_defined? work inside class bodies (with or without explicit receiver), including inside loops like [:a, :b].each { |m| define_method(m) { ... } }
  • Block parameter destructuring is supported: .map {|k, v| ...} becomes .map(([k, v]) => ...)

Collection Methods: group_by, sort_by, max_by, min_by

These methods provide Ruby-like collection operations with intelligent JavaScript output based on your ES level.

group_by

Groups elements by a key extracted from each element.

# Group users by role
users.group_by { |u| u.role }
# ES2024+: Object.groupBy(users, u => u.role)
# Pre-ES2024: users.reduce(($acc, u) => {
#   let $key = u.role;
#   ($acc[$key] = $acc[$key] ?? []).push(u);
#   return $acc
# }, {})

# Group with computed key
items.group_by { |i| i.price > 100 ? "expensive" : "affordable" }
# ES2024+: Object.groupBy(items, i => i.price > 100 ? "expensive" : "affordable")

# Destructuring support for key-value pairs
pairs.group_by { |k, v| k }
# ES2024+: Object.groupBy(pairs, ([k, v]) => k)

sort_by

Sorts elements by a key, returning a new array (non-mutating).

# Sort by property
users.sort_by { |u| u.name }
# ES2023+: users.toSorted((u_a, u_b) => {
#   if (u_a.name < u_b.name) {return -1}
#   else if (u_a.name > u_b.name) {return 1}
#   else {return 0}
# })
# Pre-ES2023: users.slice().sort(...)

# Sort by computed value
items.sort_by { |i| i.price * i.quantity }
# Compares (i.price * i.quantity) for each element

# Sort by method result (with filter chaining)
words.sort_by { |w| w.length }
# Sorts words by their length

max_by

Finds the element with the maximum value for the given key.

# Find user with highest score
users.max_by { |u| u.score }
# => users.reduce((a, b) => a.score >= b.score ? a : b)

# Find longest word
words.max_by { |w| w.length }
# => words.reduce((a, b) => a.length >= b.length ? a : b)

# Find item with highest total value
items.max_by { |i| i.price * i.qty }
# => items.reduce((a, b) => a.price * a.qty >= b.price * b.qty ? a : b)

min_by

Finds the element with the minimum value for the given key.

# Find user with lowest score
users.min_by { |u| u.score }
# => users.reduce((a, b) => a.score <= b.score ? a : b)

# Find shortest word
words.min_by { |w| w.length }
# => words.reduce((a, b) => a.length <= b.length ? a : b)

# Find cheapest item
items.min_by { |i| i.price }
# => items.reduce((a, b) => a.price <= b.price ? a : b)

Methods Always Called with Parentheses

Ruby allows calling methods without parentheses, but JavaScript requires them for methods that return values. The following methods always output with () in JS, even when written without parentheses in Ruby:

reverse, pop, shift, sort, dup, clone

arr.reverse.each { |x| puts x }
# => for (let x of arr.reverse()) { console.log(x) }

x = arr.pop
# => let x = arr.pop()

Methods Requiring Parentheses

Some Ruby method names like keys, values, index, max, etc. could also be property accesses in JavaScript (e.g., on DOM nodes). To avoid incorrect transformations, these methods are only converted when called with parentheses:

a.keys     # => a.keys (no conversion - could be property access)
a.keys()   # => Object.keys(a) (converted - clearly a method call)

The following methods require parentheses for automatic conversion: keys, values, entries, index, rindex, clear, reverse!, max, min

To force conversion even without parentheses, explicitly include the method:

Ruby2JS.convert('a.keys', include: [:keys])  # => Object.keys(a)

Or use include_all: true to enable conversion for all such methods:

Ruby2JS.convert('a.keys', include_all: true)  # => Object.keys(a)

Methods Requiring Explicit Inclusion

The following mappings will only be done if explicitly included (pass include: [:class, :call] as a convert option to enable):

  • .class .constructor
  • a.call a()

Next: Jest