Anti-Patterns

Anti-Patterns to Avoid

Some Ruby features don’t translate to JavaScript, or translate in ways that may surprise you. This guide helps you avoid common pitfalls when writing dual-target code.

Table of Contents

Metaprogramming

method_missing

Ruby’s dynamic method dispatch doesn’t exist in JavaScript:

# Won't work
class Proxy
  def method_missing(name, *args)
    @target.send(name, *args)
  end
end

Alternative: Define methods explicitly or use JavaScript’s Proxy object directly.

define_method

define_method is supported inside class bodies, including inside loops:

# This works
class Color
  %w[red green blue].each do |color|
    define_method("#{color}?") { @color == color }
  end
end

Becomes:

class Color {};
for (let color of ["red", "green", "blue"]) {
  Color.prototype[`${color}?`] = function() { return this._color == color }
}

However, define_method at the top level (outside a class) or with complex metaprogramming may not translate correctly.

eval and instance_eval

Code evaluation at runtime doesn’t translate:

# Won't work
eval("x + y")
instance_eval(&block)

Alternative: Restructure to avoid dynamic evaluation.

send and public_send

Basic send is supported:

# Static method name - works
obj.send(:foo, x, y)      # => obj.foo(x, y)

# Dynamic method name - works
obj.send(method_name, x)  # => obj[method_name](x)

However, public_send is not specifically handled (treated the same as send), and complex dispatch patterns may not translate correctly.

Type Introspection

is_a?, kind_of?, instance_of?

These methods are fully supported with the functions filter:

obj.is_a?(MyClass)    # => obj instanceof MyClass
obj.kind_of?(MyClass) # => obj instanceof MyClass
obj.instance_of?(MyClass) # => obj.constructor === MyClass

Built-in types are handled specially:

obj.is_a?(Array)   # => Array.isArray(obj)
obj.is_a?(String)  # => typeof obj === "string"
obj.is_a?(Integer) # => typeof obj === "number" && Number.isInteger(obj)
obj.is_a?(Float)   # => typeof obj === "number"
obj.is_a?(Hash)    # => typeof obj === "object" && obj !== null && !Array.isArray(obj)

respond_to?

Method/property existence checks work:

obj.respond_to?(:save)  # => "save" in obj
obj.respond_to?("save") # => "save" in obj

class and superclass

These are supported with the functions filter:

obj.class        # => obj.constructor
obj.class.name   # => obj.constructor.name
obj.class == Foo # => obj.constructor === Foo

Foo.superclass   # => Object.getPrototypeOf(Foo.prototype).constructor

Ruby-Specific Features

Symbols vs Strings

Ruby symbols don’t exist in JavaScript:

# These become identical in JS
:foo     # => "foo"
"foo"    # => "foo"

# Hash keys work, but type distinction is lost
{ foo: 1 }        # => { foo: 1 }
{ "foo" => 1 }    # => { "foo": 1 }

Ranges as Objects

Ruby ranges are objects with methods:

# Works (converted to loops or conditions)
(1..10).each { |i| puts i }
(1...5).to_a

# Problematic - ranges as first-class objects
range = (1..10)
range.include?(5)  # May not work as expected

Alternative: Use explicit loops or arrays.

Multiple Return Values

Ruby’s implicit array unpacking differs from JS:

# Ruby
def pair; [1, 2]; end
a, b = pair  # Works

# JavaScript
# let [a, b] = pair()  # Works too, but be explicit

This generally works, but be aware of edge cases with nested destructuring.

Default Mutable Arguments

A classic Ruby gotcha that’s different in JS:

# Ruby gotcha - shared mutable default
def add(item, list = [])
  list << item
end

# JavaScript doesn't share defaults, but still avoid:
def add(item, list = [])
  list.push(item)  # Creates new array each call in JS
end

Control Flow Differences

retry

Ruby’s retry in exception handling doesn’t exist in JS:

# Won't work
begin
  attempt_operation
rescue
  retry if should_retry?
end

Alternative: Use explicit loops:

loop do
  begin
    attempt_operation
    break
  rescue
    next if should_retry?
    raise
  end
end

redo

Loop redo doesn’t translate:

# Won't work
items.each do |item|
  redo if needs_retry?(item)
end

Alternative: Restructure with explicit control flow.

catch / throw

Ruby’s catch/throw (not exceptions) doesn’t exist in JS:

# Won't work
catch(:done) do
  items.each do |item|
    throw :done if found?(item)
  end
end

Alternative: Use break, return, or exceptions.

Object Model Differences

Singleton Methods

Instance-level method definition doesn’t work:

# Won't work
obj = Object.new
def obj.custom_method
  "custom"
end

Module Mixins

include and extend are supported:

module Loggable
  def log(msg)
    puts msg
  end
end

class MyClass
  include Loggable  # Add instance methods
end

class MyClass
  extend Loggable   # Add class methods
end

Generates:

const Loggable = {log(msg) {console.log(msg)}};

class MyClass {}
Object.defineProperties(MyClass.prototype, Object.getOwnPropertyDescriptors(Loggable));

class MyClass {}
Object.defineProperties(MyClass, Object.getOwnPropertyDescriptors(Loggable));

This also works with anonymous classes:

filter = Class.new { include Loggable }

prepend

Method prepending doesn’t exist in JS:

# Won't work
module Wrapper
  def process
    pre_process
    super
    post_process
  end
end

class MyClass
  prepend Wrapper
end

Numeric Precision

BigDecimal

Ruby’s arbitrary precision decimals don’t exist natively in JS:

# Ruby-only
require 'bigdecimal'
price = BigDecimal("19.99")

Alternative: Use JavaScript libraries or work with cents:

price_cents = 1999  # Store as integer cents

Integer Division

Ruby and JavaScript handle division differently:

# Ruby
5 / 2    # => 2 (integer division)
5.0 / 2  # => 2.5

# JavaScript
5 / 2    // => 2.5 (always float)

Use Math.floor() for integer division in JS contexts.

String Differences

Encoding

Ruby’s encoding system doesn’t translate:

# Ruby-only
str.encoding
str.force_encoding("UTF-8")

Character Iteration

# Ruby - iterates codepoints properly
"emoji: 😀".each_char { |c| puts c }

# JavaScript - may split surrogate pairs
# Use Array.from() for proper iteration

Summary: Safe vs Unsafe

Feature Status Notes
Classes, methods ✅ Safe  
Blocks as lambdas ✅ Safe  
Arrays, hashes ✅ Safe Mind <<, .dup
String interpolation ✅ Safe  
Control flow ✅ Safe Except retry, redo
define_method ✅ Safe In class bodies
send ✅ Safe Static or dynamic names
include/extend ✅ Safe Module mixins
is_a?, kind_of? ✅ Safe Maps to instanceof
instance_of? ✅ Safe Exact type check
respond_to? ✅ Safe Property existence
.class ✅ Safe Returns constructor
superclass ✅ Safe Parent class
Symbols ⚠️ Caution Work as strings
method_missing ❌ Avoid Explicit methods
eval ❌ Avoid Restructure
prepend ❌ Avoid MRO incompatible
retry/redo ❌ Avoid Explicit loops
catch/throw ❌ Avoid Break/return

See Also

Next: JavaScript-Only Development