Primate

Ruby

Primate runs Ruby with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing.

Setup

Install Ruby

First, install Ruby 3.0 or later from the official website.

Install Module

npm install @primate/ruby

Configure

import config from "primate/config";
import ruby from "@primate/ruby";

export default config({
  modules: [ruby()],
});

Initialize Ruby

Create a Gemfile in your project root:

bundle init

Add the Primate Ruby dependency:

# Gemfile
source "https://rubygems.org"

gem "primate-run", "~> 0.1.0"

Install dependencies to vendor/bundle (required for WASM):

bundle config set --local path 'vendor/bundle'
bundle install

This creates Gemfile and Gemfile.lock files that manage your Ruby dependencies.

Routes

Create Ruby route handlers in routes using .rb files. Routes are compiled to WebAssembly and run in the JavaScript runtime.

# routes/hello.rb
require 'primate/route'

Route.get do |request|
  'Hello, world!'
end

HTTP Methods

All standard HTTP methods are supported:

require 'primate/route'

Route.get do |request|
  'GET request'
end

Route.post do |request|
  'POST request'
end

Route.put do |request|
  'PUT request'
end

Route.delete do |request|
  'DELETE request'
end

Request Handling

Query Parameters

Access query parameters through the query request bag:

# routes/query.rb
require 'primate/route'

Route.get do |request|
  if request.query.has?('foo')
    request.query.get('foo')
  else
    'foo missing'
  end
end

Request Body

Handle different body types based on content:

JSON Body

# routes/json.rb
require 'primate/route'

Route.post do |request|
  begin
    request.body.json
  rescue => e
    { error: e.message }
  end
end

Form Fields

# routes/form.rb
require 'primate/route'

Route.post do |request|
  begin
    request.body.fields
  rescue => e
    { error: e.message }
  end
end

Text Body

# routes/text.rb
require 'primate/route'

Route.post do |request|
  request.body.text
end

Binary Data

# routes/binary.rb
require 'primate/route'

Route.post do |request|
  readable = request.body.binary

  # Get first 4 bytes for file type detection
  head = readable.head(4)

  {
    type: readable.content_type,
    size: readable.size,
    head: head
  }
end

File Uploads

Handle multipart file uploads:

# routes/upload.rb
require 'primate/route'

Route.post do |request|
  begin
    # Get form fields
    fields = request.body.fields

    # Get uploaded files
    files = request.body.files

    # Process files
    file_info = files.map do |file|
      {
        field: file.field,        # form field name
        name: file.filename,      # original filename
        type: file.content_type,  # MIME type
        size: file.size          # file size in bytes
      }
    end

    {
      fields: fields,
      files: file_info
    }
  rescue => e
    { error: e.message }
  end
end

Validation

Use Primate's strongly-typed validation system with the pema module:

# routes/validate.rb
require 'primate/route'
require 'primate/pema'

schema = Pema.schema({
  'baz' => Pema.int,
  'foo' => Pema.string
})

Route.get do |request|
  begin
    request.query.parse(schema, true)
  rescue Pema::ValidationError => e
    e.message
  end
end

Field Types

The validation system supports multiple strongly-typed field types:

Coercion

Enable automatic type coercion by passing true as the second parameter:

request.query.parse(schema, true) # enables coercion

With coercion enabled:

Responses

Plain Data

Return any Ruby object that can be JSON serialized:

Route.get do |request|
  { name: 'Donald' }
end

Route.get do |request|
  'Hello, world!'
end

Route.get do |request|
  [
    { name: 'Donald' },
    { name: 'Ryan' }
  ]
end

Views

Render components with props:

# routes/view.rb
require 'primate/route'
require 'primate/response'

Route.get do |request|
  Response.view('index.html', hello: 'world')
end

With options:

require 'primate/route'
require 'primate/response'

Route.get do |request|
  Response.view('index.html',
    { hello: 'world' },
    { partial: true }
  )
end

Redirects

Redirect to another route:

# routes/redirect.rb
require 'primate/route'
require 'primate/response'

Route.get do |request|
  Response.redirect('/redirected')
end

With custom status code:

Route.get do |request|
  Response.redirect('/redirected', status: 301) # moved permanently
end

Error Responses

Return error responses:

# routes/error.rb
require 'primate/route'
require 'primate/response'

Route.get do |request|
  Response.error
end

With custom error options:

Route.get do |request|
  Response.error(body: 'Custom error message')
end

Sessions

Manage user sessions with the session module:

# routes/session.rb
require 'primate/route'
require 'primate/session'

Route.get do |request|
  # Create a session
  Session.create(foo: 'bar')

  # Get session data
  Session.get
end

Session Methods

Ruby Conventions

Primate Ruby follows standard Ruby conventions:

Method Names

Hash Syntax

Use modern Ruby hash syntax with symbols:

# Preferred
{ name: 'John', age: 30 }

# Also valid
{ :name => 'John', :age => 30 }

String Literals

Use single quotes for plain strings, double quotes for interpolation:

# Plain strings
message = 'Hello, world!'

# String interpolation
greeting = "Hello, #{name}!"

Configuration

Option Type Default Description
fileExtension string ".rb" Associated file extension

Example

import ruby from "@primate/ruby";
import config from "primate/config";

export default config({
  modules: [
    ruby({
      // use `.ruby` as associated file extension
      fileExtension: ".ruby",
    }),
  ],
});

Resources

Previous
Python
Next
Intro