Skip to main content
LiveCodes supports Ruby execution in the browser using Opal (Ruby-to-JavaScript compiler) and ruby.wasm (CRuby via WebAssembly).

Configuration

Language Name: ruby (Opal) or ruby-wasm (CRuby)
File Extensions: .rb
Editor: Script editor
Compiler: Opal compiler or ruby.wasm
Runtime: Opal stdlib with auto-loading

Ruby with Opal

Opal compiles Ruby to JavaScript, allowing Ruby code to run in the browser:
# Basic Ruby syntax
name = "World"
puts "Hello, #{name}!"

# Variables and types
age = 30
pi = 3.14
is_active = true

# String interpolation
puts "Age: #{age}, Pi: #{pi}"

# Arrays
numbers = [1, 2, 3, 4, 5]
puts numbers.inspect

# Hashes
user = {
  name: "John Doe",
  age: 30,
  email: "[email protected]"
}
puts user[:name]

Methods

# Method definition
def greet(name, greeting = "Hello")
  "#{greeting}, #{name}!"
end

puts greet("Alice")
puts greet("Bob", "Hi")

# Method with block
def repeat(n)
  n.times { |i| yield i }
end

repeat(3) { |i| puts "Iteration #{i}" }

# Multiple return values
def divide(a, b)
  return nil, "Division by zero" if b == 0
  return a / b, nil
end

result, error = divide(10, 2)
if error
  puts "Error: #{error}"
else
  puts "Result: #{result}"
end

Classes and Objects

class Person
  attr_accessor :name, :age
  attr_reader :email
  
  def initialize(name, age, email)
    @name = name
    @age = age
    @email = email
  end
  
  def greet
    "Hello, I'm #{@name}"
  end
  
  def have_birthday
    @age += 1
  end
  
  def to_s
    "#{@name} (#{@age})"
  end
end

person = Person.new("Alice", 25, "[email protected]")
puts person.greet

person.have_birthday
puts "New age: #{person.age}"
puts person.to_s

Blocks, Procs, and Lambdas

# Blocks
[1, 2, 3, 4, 5].each { |n| puts n * 2 }

# Multi-line blocks
[1, 2, 3, 4, 5].each do |n|
  squared = n ** 2
  puts "#{n} squared is #{squared}"
end

# Proc
multiplier = Proc.new { |x, y| x * y }
puts multiplier.call(3, 4)

# Lambda
adder = ->(x, y) { x + y }
puts adder.call(5, 3)

# Block as parameter
def process_numbers(numbers, &block)
  numbers.map(&block)
end

result = process_numbers([1, 2, 3, 4, 5]) { |n| n * 2 }
puts result.inspect

Enumerables

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Map
squared = numbers.map { |n| n ** 2 }
puts "Squared: #{squared.inspect}"

# Select (filter)
even = numbers.select { |n| n.even? }
puts "Even: #{even.inspect}"

# Reject
odd = numbers.reject { |n| n.even? }
puts "Odd: #{odd.inspect}"

# Reduce
sum = numbers.reduce(0) { |acc, n| acc + n }
puts "Sum: #{sum}"

# Any? and All?
has_even = numbers.any?(&:even?)
all_positive = numbers.all? { |n| n > 0 }
puts "Has even: #{has_even}, All positive: #{all_positive}"

# Find
first_even = numbers.find(&:even?)
puts "First even: #{first_even}"

String Methods

text = "Hello, World!"

# Case manipulation
puts text.upcase
puts text.downcase
puts text.capitalize
puts text.swapcase

# Substring
puts text[0..4]  # "Hello"
puts text[-6..-1]  # "World!"

# Split and join
words = text.split(", ")
puts words.inspect
puts words.join(" - ")

# Include?
puts text.include?("World")  # true

# Replace
new_text = text.gsub("World", "Ruby")
puts new_text

# Strip
padded = "  hello  "
puts padded.strip

DOM Manipulation

Access browser DOM through Opal:
require 'native'

# Access document
document = Native(`document`)

# Create element
div = document.createElement('div')
div.textContent = 'Hello from Ruby!'
div.classList.add('container')

# Append to body
body = document.querySelector('body')
body.appendChild(div)

# Create button
button = document.createElement('button')
button.textContent = 'Click me'

# Add event listener
button.addEventListener('click') do |event|
  `alert('Button clicked!')`
  event.target.textContent = 'Clicked!'
end

body.appendChild(button)

Counter Example

require 'native'

count = 0
document = Native(`document`)

# Create display
display = document.createElement('div')
display.id = 'display'
display.textContent = "Count: #{count}"
document.body.appendChild(display)

# Increment button
inc_button = document.createElement('button')
inc_button.textContent = 'Increment'
inc_button.addEventListener('click') do
  count += 1
  document.getElementById('display').textContent = "Count: #{count}"
end
document.body.appendChild(inc_button)

# Decrement button
dec_button = document.createElement('button')
dec_button.textContent = 'Decrement'
dec_button.addEventListener('click') do
  count -= 1
  document.getElementById('display').textContent = "Count: #{count}"
end
document.body.appendChild(dec_button)

Modules and Mixins

module Greetable
  def greet
    "Hello, #{name}!"
  end
end

module Timestamps
  def created_at
    @created_at ||= Time.now
  end
end

class User
  include Greetable
  include Timestamps
  
  attr_accessor :name
  
  def initialize(name)
    @name = name
  end
end

user = User.new("Alice")
puts user.greet
puts user.created_at

Exception Handling

def divide(a, b)
  raise "Division by zero" if b == 0
  a / b
end

begin
  result = divide(10, 2)
  puts "Result: #{result}"
  
  result = divide(10, 0)
rescue => e
  puts "Error: #{e.message}"
ensure
  puts "Always executed"
end

# Custom exceptions
class ValidationError < StandardError
end

def validate_age(age)
  raise ValidationError, "Age must be positive" if age < 0
  raise ValidationError, "Age too high" if age > 150
  true
end

begin
  validate_age(-5)
rescue ValidationError => e
  puts "Validation failed: #{e.message}"
end

Configuration Options

Configure Opal compiler:
{
  "customSettings": {
    "ruby": {
      "autoloadStdlib": true,
      "requireMap": {
        "custom_gem": "https://cdn.example.com/gem.js"
      },
      "arity_check": false,
      "freezing": true
    }
  }
}

Opal Compiler Options

  • arity_check: Enable/disable method arity checking
  • freezing: Enable/disable object freezing
  • autoloadStdlib: Auto-load Opal standard library
  • requireMap: Map require paths to URLs

Standard Library

Opal includes many Ruby standard library features:
require 'date'
require 'json'
require 'set'

# Date and Time
today = Date.today
puts today.to_s

# JSON
data = { name: "Alice", age: 25 }
json_str = JSON.generate(data)
puts json_str

parsed = JSON.parse(json_str)
puts parsed['name']

# Set
set = Set.new([1, 2, 3, 2, 1])
puts set.inspect  # #<Set: {1, 2, 3}>
set.add(4)
puts set.include?(3)  # true

Example Projects

Todo List

require 'native'

class TodoApp
  def initialize
    @todos = []
    @document = Native(`document`)
    setup_ui
  end
  
  def setup_ui
    container = @document.createElement('div')
    container.className = 'todo-app'
    
    # Input
    @input = @document.createElement('input')
    @input.placeholder = 'Add a todo...'
    @input.addEventListener('keypress') do |e|
      add_todo if e.key == 'Enter'
    end
    
    # Add button
    add_btn = @document.createElement('button')
    add_btn.textContent = 'Add'
    add_btn.addEventListener('click') { add_todo }
    
    # List
    @list = @document.createElement('ul')
    
    container.appendChild(@input)
    container.appendChild(add_btn)
    container.appendChild(@list)
    @document.body.appendChild(container)
  end
  
  def add_todo
    text = @input.value.strip
    return if text.empty?
    
    @todos << { id: Time.now.to_i, text: text, done: false }
    @input.value = ''
    render
  end
  
  def toggle_todo(id)
    todo = @todos.find { |t| t[:id] == id }
    todo[:done] = !todo[:done] if todo
    render
  end
  
  def delete_todo(id)
    @todos.reject! { |t| t[:id] == id }
    render
  end
  
  def render
    @list.innerHTML = ''
    
    @todos.each do |todo|
      item = @document.createElement('li')
      item.style.textDecoration = 'line-through' if todo[:done]
      
      checkbox = @document.createElement('input')
      checkbox.type = 'checkbox'
      checkbox.checked = todo[:done]
      checkbox.addEventListener('change') { toggle_todo(todo[:id]) }
      
      text = @document.createTextNode(todo[:text])
      
      delete_btn = @document.createElement('button')
      delete_btn.textContent = 'Delete'
      delete_btn.addEventListener('click') { delete_todo(todo[:id]) }
      
      item.appendChild(checkbox)
      item.appendChild(text)
      item.appendChild(delete_btn)
      @list.appendChild(item)
    end
  end
end

TodoApp.new

Array Operations

# Functional programming style
numbers = (1..10).to_a

# Chaining operations
result = numbers
  .select(&:even?)
  .map { |n| n ** 2 }
  .reduce(:+)

puts "Sum of squared evens: #{result}"

# Partition
even, odd = numbers.partition(&:even?)
puts "Even: #{even.inspect}"
puts "Odd: #{odd.inspect}"

# Group by
grouped = numbers.group_by { |n| n % 3 }
puts grouped.inspect

# Zip
letters = ['a', 'b', 'c']
zipped = numbers.first(3).zip(letters)
puts zipped.inspect  # [[1, "a"], [2, "b"], [3, "c"]]

Ruby.wasm (WebAssembly)

For full CRuby support, use ruby-wasm:
# Full CRuby 3.x support
require 'json'

data = {
  users: [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 }
  ]
}

puts JSON.pretty_generate(data)
ruby.wasm provides full CRuby support but has a larger download size (~15MB) compared to Opal.

Best Practices

Prefer symbols for hash keys:
# Good
user = { name: 'Alice', age: 25 }

# Less efficient
user = { 'name' => 'Alice', 'age' => 25 }
Leverage Ruby’s block syntax:
# Good
numbers.select(&:even?)

# Verbose
numbers.select { |n| n.even? }
Use safe navigation operator:
# Good
user&.profile&.name

# Verbose
user && user.profile && user.profile.name

Limitations

Opal Differences: Opal is not 100% compatible with CRuby. Some features may work differently or be unavailable.
No Native Extensions: C extensions and native gems are not supported in browser environments.
For full CRuby compatibility, use ruby-wasm, though it has a larger initial load time.

Python

Similar dynamic language

JavaScript

Browser native language

CoffeeScript

Ruby-inspired JavaScript

PHP

Server-side scripting

Build docs developers (and LLMs) love