Ruby SDK

Ruby Quick Start

WoWSQL Ruby SDK — Get started in minutes

Get your API keys: In your WoWSQL project dashboard, go to Settings → API to find your anon key (wowsql_anon_...) and service role key (wowsql_service_...). The anon key is safe for client-side use (respects Row Level Security). The service role key bypasses RLS and should only be used server-side.

Installation

Official package: View on RubyGems →

Direct install:

gem install wowsql-sdk

Gemfile:

# Gemfile
gem 'wowsql-sdk', '~> 3.10'

# Then run
bundle install

Initialize Client

The SDK talks to PostgREST at /rest/v1/. Your project URL resolves to https://myproject.wowsqlconnect.com.

require 'wowsql'

# Initialize client with your anon key (respects Row Level Security)
client = WOWSQL::WOWSQLClient.new(
  'myproject',                                    # resolves to https://myproject.wowsqlconnect.com
  'wowsql_anon_your_key_here'                     # anon key from Settings → API
)

# Or with full URL
client_full = WOWSQL::WOWSQLClient.new(
  'https://myproject.wowsqlconnect.com',
  'wowsql_anon_your_key_here'
)

# Always use select() before get()
users = client.table('users').select('*').get

Query Data

Basic Queries

# Get all records
users = client.table('users').select('*').get

# With filters
adults = client.table('users')
  .select('*')
  .gte('age', 18)
  .get

# Select specific columns
names = client.table('users')
  .select('id', 'name')
  .get

Advanced Queries

# Multiple filters with sorting
active_users = client.table('users')
  .select('*')
  .eq('status', 'active')
  .gt('age', 18)
  .order_by('created_at', 'desc')
  .limit(20)
  .offset(0)
  .get

puts "Found #{active_users['count']} users"
active_users['data'].each { |user| puts user['name'] }

Filter Helper Methods

# Using helper methods
users = client.table('users')
  .eq('status', 'active')
  .gt('age', 18)
  .like('email', '%@gmail.com')
  .is_not_null('email_verified_at')
  .in_list('role', %w[admin moderator])
  .between('created_at', '2024-01-01', '2024-12-31')
  .get

GROUP BY and Aggregates

Basic GROUP BY

# Group by category with counts
result = client.table('products')
  .select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
  .group_by('category')
  .get

# Group by date
result2 = client.table('orders')
  .select('DATE(created_at) as date', 'COUNT(*) as orders', 'SUM(total) as revenue')
  .group_by('DATE(created_at)')
  .order_by('date', 'desc')
  .get

HAVING Clause

# Filter aggregated results
result = client.table('products')
  .select('category', 'COUNT(*) as count')
  .group_by('category')
  .having('COUNT(*)', 'gt', 10)
  .get

CRUD Operations

Create Record

result = client.table('users').create(
  name: 'John Doe',
  email: 'john@example.com',
  age: 30
)
puts "Created user with ID: #{result['id']}"

Update Record

client.table('users').update(123, email: 'newemail@example.com')
puts 'User updated successfully'

Delete Record

client.table('users').delete(123)
puts 'User deleted successfully'

Get Single Record by ID

user = client.table('users').get_by_id(123)
puts "User: #{user['name']}"

Get First Record

user = client.table('users')
  .eq('status', 'active')
  .first

puts "First active user: #{user['name']}" if user

Filter Operators

Available operators for filtering data:

OperatorDescriptionExample (Ruby)
eqEquals.eq('status', 'active')
neqNot equals.neq('status', 'deleted')
gtGreater than.gt('age', 18)
gteGreater than or equal.gte('age', 18)
ltLess than.lt('price', 100)
lteLess than or equal.lte('price', 100)
likePattern matching.like('name', 'john')
isIS NULL / IS NOT NULL.is_null('deleted_at') / .is_not_null('email')
inIN operator.in('category', %w[electronics books])
not_inNOT IN.not_in('status', %w[deleted archived])
betweenBETWEEN.between('price', 10, 100)

Error Handling

begin
  users = client.table('users').select('*').get
  puts "Success: #{users['data'].count} users"
rescue WOWSQL::StorageException => e      # alias of WOWSQL::StorageError (rescue the subclass first)
  puts "Storage error: #{e.message}"
rescue WOWSQL::WOWSQLException => e       # alias of WOWSQL::WOWSQLError
  puts "Database error (#{e.status_code}): #{e.message}"
end

Rails Integration

Use a global client via Rails credentials:

# config/initializers/wowsql.rb
WOWSQL_CLIENT = WOWSQL::WOWSQLClient.new(
  Rails.application.credentials.wowsql[:project_url],
  Rails.application.credentials.wowsql[:anon_key]    # wowsql_anon_... from Settings → API
)

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = WOWSQL_CLIENT.table('users')
      .select('id', 'name', 'email')
      .get['data']
  end
end

Authentication

Auth endpoints are served at /auth/v1/. Use your anon key as the apikey header.

Sign Up & Sign In

auth = WOWSQL::ProjectAuthClient.new('myproject', 'wowsql_anon_your_key_here')

# Sign up (POST /auth/v1/signup)
result = auth.sign_up(
  email: 'user@example.com',
  password: 'SecurePassword123',
  full_name: 'John Doe'
)
puts "Access token: #{result.session.access_token}"

# Sign in (POST /auth/v1/login) — returns an AuthResponse (a session, or a 2FA challenge); fetch the user with get_user
login = auth.sign_in(email: 'user@example.com', password: 'SecurePassword123')
if login.requires_2fa
  # TOTP is on for this user: finish with verify_two_factor_login (see Two-Factor Authentication)
  puts "2FA required, method: #{login.method}"
else
  puts "User ID: #{auth.get_user.id}"
end

CAPTCHA (Turnstile)

Optional. Only required when dashboard Attack Protection → Turnstile is enabled. Create a Cloudflare Turnstile widget for your app domain and paste the site key and secret in Attack Protection (WoWSQL does not share a platform widget). GET /auth/v1/settings returns your captcha.site_key. Render Turnstile with that key, then pass the token. Omit it when captcha is off — existing calls are unchanged.

auth.sign_up(email: 'user@example.com', password: 'SecurePassword123', captcha_token: turnstile_token)
auth.sign_in(email: 'user@example.com', password: 'SecurePassword123', captcha_token: turnstile_token)
auth.forgot_password(email: 'user@example.com', captcha_token: turnstile_token)
auth.send_otp(email: 'user@example.com', captcha_token: turnstile_token)
auth.send_magic_link(email: 'user@example.com', captcha_token: turnstile_token)
auth.resend_verification(email: 'user@example.com', captcha_token: turnstile_token)

Password Reset

auth.forgot_password(email: 'user@example.com')
auth.reset_password(token: 'reset_token_from_email', new_password: 'NewSecurePassword123')

OTP Authentication

Provide exactly one of email or phone. Phone OTP requires Auth → SMS / Phone OTP (Twilio, Fast2SMS, or MSG91).

# Email OTP
auth.send_otp(email: 'user@example.com', purpose: 'login')
email_login = auth.verify_otp(email: 'user@example.com', otp: '123456', purpose: 'login')

# Phone OTP (SMS)
auth.send_otp(phone: '+15551234567', purpose: 'login')
phone_login = auth.verify_otp(phone: '+15551234567', otp: '123456', purpose: 'login')
puts phone_login
auth.send_magic_link(email: 'user@example.com', purpose: 'login')
auth.verify_email(token: 'verification_token_from_email')
auth.resend_verification(email: 'user@example.com')

OAuth Authentication

Setup checklist — do this once per provider, per project:
1. Dashboard → Project → Auth → Providers: enable the provider, paste Client ID and Client Secret (no leading/trailing spaces).
2. Copy the Redirect URI shown: https://<slug>.wowsqlconnect.com/auth/v1/oauth/<provider>/callback and register it in your provider's developer console.
3. The callback URL is handled entirely by WoWSQL — your app does not send an API key on it. WoWSQL exchanges the code and redirects to your frontend_redirect_uri with ?access_token=&refresh_token=.

# ── Step 1: Server-side — get the authorization URL ─────────────────────────
result = auth.get_oauth_authorization_url(
  provider: 'google',
  redirect_uri: 'https://yourapp.com/auth/callback'   # sent as frontend_redirect_uri
)
# Redirect the browser to result['authorization_url']
# Google calls: https://<slug>.wowsqlconnect.com/auth/v1/oauth/google/callback
# WoWSQL exchanges the code and redirects to your redirect_uri.


# ── Step 2: Frontend — read tokens from the redirect ────────────────────────
# WoWSQL redirects to: https://yourapp.com/auth/callback?access_token=...&refresh_token=...
# Parse them in your frontend JS / ERB template.


# ── (Advanced) Manual code exchange — only if you handle the callback yourself
auth_result = auth.exchange_oauth_callback(
  provider: 'google',
  code: 'authorization_code_from_callback',
  redirect_uri: 'https://yourapp.com/auth/callback'  # optional; the server exchanges the code with the project callback URL
)
puts "OAuth login successful: #{auth_result.session.access_token}"

Session and Token Refresh

Token refresh calls POST /auth/v1/refresh-token with the stored refresh token and stores the new token pair. From 3.10.0 every SDK uses this route; before 3.10.0 the TypeScript and Java SDKs called routes that do not exist, and Flutter and Rust had no refresh method.

# Current user → GET /auth/v1/me (uses the stored access token)
me = auth.get_user

# Refresh tokens → POST /auth/v1/refresh-token (uses the stored refresh token)
refreshed = auth.refresh_token
puts refreshed.session.access_token

# Read or restore the stored session
tokens = auth.get_session
auth.set_session(access_token: 'saved_access_token', refresh_token: 'saved_refresh_token')

# Sign out → POST /auth/v1/logout
auth.logout

Two-Factor Authentication (TOTP)

Added in 3.10.0. When a user has TOTP two-factor authentication enabled, sign-in does not return a session. It returns a challenge instead: requires_2fa is true and a short-lived pending_token is set (SDK field names are in the example). Finish the sign-in with the verify call, using a 6-digit code from the authenticator app or one of the user's backup codes. Status, setup, enable, disable and recovery-code calls run as the signed-in user: the SDK sends your API key and the stored access token. Enabling 2FA and rotating recovery codes return new backup codes once; show them to the user and do not store them in plain text.

Ruby 3.10.0 does not wrap /settings, anonymous sign-in, identity linking or email change; only the TypeScript, Python and Java SDKs do.

# 1. Enrol (user is signed in)
setup = auth.setup_totp               # POST /auth/v1/2fa/setup-totp
# Show setup['qr_code_url'] (a PNG data URI when available) or setup['otpauth_uri']
enabled = auth.enable_totp('123456')  # POST /auth/v1/2fa/verify-and-enable
puts enabled['backup_codes']          # shown once

# 2. Sign in when 2FA is on
login = auth.sign_in(email: 'user@example.com', password: 'SecurePassword123')
if login.requires_2fa
  # POST /auth/v1/2fa/verify-login
  done = auth.verify_two_factor_login(login.pending_token, code: '123456')
  # or: backup_code: 'one-of-your-backup-codes'
  puts done.session.access_token
end

# 3. Manage
status = auth.get_two_factor_status              # enabled, method
rotated = auth.regenerate_recovery_codes('123456')
auth.disable_totp(code: '123456')                # or backup_code: '...'

Storage Operations

Initialize Storage

Storage endpoints are served at /storage/v1/. The SDK storage client itself calls /api/v1/storage/projects/{project_slug}/… on your project host (buckets, files, stats).

storage = WOWSQL::WOWSQLStorage.new(
  'myproject',
  'wowsql_anon_your_key_here'    # anon key
)

File Operations

# Upload a local file: upload_from_path(file_path, bucket_name:, path:)
result = storage.upload_from_path('path/to/document.pdf', bucket_name: 'documents', path: 'uploads/document.pdf')
puts "Uploaded: #{result}"

# Public URL (public buckets): get_public_url(bucket_name, file_path)
url = storage.get_public_url('documents', 'uploads/document.pdf')
puts "Public URL: #{url}"

# Download bytes, or save straight to disk
bytes = storage.download('documents', 'uploads/document.pdf')
storage.download_to_file('documents', 'uploads/document.pdf', '/tmp/document.pdf')

# List files: list_files(bucket_name, prefix:, limit:, offset:)
files = storage.list_files('documents', prefix: 'uploads/')
files.each { |file| puts "#{file.path}: #{file.size_mb.round(2)} MB" }

# Delete file
storage.delete_file('documents', 'uploads/document.pdf')

# Check quota
quota = storage.get_quota
puts "Used: #{quota.total_size_gb} GB across #{quota.total_files} files"

Download or View File

Via REST: use GET /storage/v1/object/{bucket}/{path} for download, or GET /storage/v1/object/public/{bucket}/{path} for public access.

Realtime

Subscribe to INSERT / UPDATE / DELETE, broadcast ephemeral events, and track presence. Uses the same anon or service_role key as REST.

WebSocket URL: wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>. Auth is always the query param. Enable a table first with POST /realtime/v1/enable and body {"schema_name":"public","table_name":"messages"}. Channel send / presence do not need a Postgres trigger. Payloads are capped at 64 KiB. Missing key closes 4001; invalid key closes 4003 (do not reconnect). Multi-replica deployments need REDIS_URL.

unsub = client.realtime.subscribe("messages") do |change|
  puts "#{change['event']} #{change['new'] || change['old']}"
end

channel = client.realtime.channel("chat")
channel.on("broadcast", event: "typing") { |msg| puts msg["payload"] }
channel.on("presence") { |msg| puts channel.presence_state }
channel.subscribe
channel.track("user" => "alice")
channel.send(event: "typing", payload: { "user" => "alice" })

Schema DDL (WOWSQLSchema)

Requires service role key. create_table requires keyword primary_key: naming a column whose type is UUID. Use auto_increment on that column for DEFAULT gen_random_uuid(). Raw CREATE TABLE via execute SQL cannot use SERIAL PRIMARY KEY (or similar integer PK). Existing tables are unchanged.

schema = WOWSQL::WOWSQLSchema.new(
  'myproject',
  'wowsql_service_your_key_here'    # service role key — bypasses RLS
)

schema.create_table('items', [
  { 'name' => 'id', 'type' => 'UUID', 'auto_increment' => true },
  { 'name' => 'title', 'type' => 'TEXT' },
], primary_key: 'id')