WowSQL Ruby SDK
The official Ruby SDK for WowSQL. Provides a clean, chainable interface for all PostgREST database operations, authentication, file storage, and schema management.
Table of Contents
- Requirements
- Installation
- Quick Start
- Client Configuration
- Database Operations
- Query Builder
- Authentication
- File Storage
- Schema Management
- Error Handling
- Response Format
Requirements
- Ruby 2.7 or higher
faraday>= 1.0faraday-multipart>= 1.0
Installation
Add to your Gemfile:
gem 'wowsql-sdk'
Or install directly:
gem install wowsql-sdk
Quick Start
require 'wowsql'
# Initialize client with your project slug and API key
client = WOWSQL::WOWSQLClient.new("myproject", "wowsql_anon_...")
# Insert a record
user = client.table("users").create({ email: "alice@example.com", name: "Alice" })
# Query with filters
result = client.table("users")
.select("id", "email", "name")
.eq("is_active", true)
.order_by("created_at", "desc")
.limit(10)
.get
result["data"].each { |u| puts u["email"] }
# Close when done
client.close
Client Configuration
client = WOWSQL::WOWSQLClient.new(
"myproject", # Project slug, full hostname, or full URL
"wowsql_anon_...", # Anonymous key (or service role key for privileged ops)
base_domain: "wowsqlconnect.com", # Default; only needed if self-hosting
secure: true, # Use HTTPS (default: true)
timeout: 30, # Request timeout in seconds (default: 30)
verify_ssl: true # Verify SSL certificates (default: true)
)
project_url formats accepted:
| Format | Description |
|---|---|
"myproject" |
Appends .wowsqlconnect.com automatically |
"myproject.wowsqlconnect.com" |
Full hostname |
"https://myproject.wowsqlconnect.com" |
Full URL |
"https://your-self-hosted-domain.com" |
Self-hosted instance |
API Key types:
| Key Prefix | Purpose |
|---|---|
wowsql_anon_... |
Public / client-side operations |
wowsql_service_... |
Server-side, privileged operations (schema management, admin) |
Database Operations
get — Query Records
# All records
result = client.table("products").get
# Chained query
result = client.table("products")
.select("id", "name", "price")
.eq("category", "electronics")
.order_by("price", "asc")
.limit(20)
.offset(0)
.get
puts result["data"] # Array of hashes
puts result["count"] # Records returned
puts result["total"] # Total matching records
puts result["limit"] # Applied limit
puts result["offset"] # Applied offset
get_by_id — Fetch by Primary Key
user = client.table("users").get_by_id("550e8400-e29b-41d4-a716-446655440000")
puts user["email"]
create / insert — Insert a Record
product = client.table("products").create({
name: "Widget Pro",
price: 29.99,
category: "tools",
in_stock: true
})
puts product["id"]
insert is an alias for create.
bulk_insert — Insert Multiple Records
records = [
{ name: "Item A", price: 10.00 },
{ name: "Item B", price: 20.00 },
{ name: "Item C", price: 30.00 }
]
results = client.table("products").bulk_insert(records)
puts "Inserted #{results.length} records"
upsert — Insert or Update
# Inserts if not exists; updates if the id already exists
record = client.table("settings").upsert(
{ id: "user-uuid", theme: "dark", language: "en" },
on_conflict: "id"
)
update — Update by ID
updated = client.table("users").update(
"550e8400-e29b-41d4-a716-446655440000",
{ name: "Alice Smith", updated_at: Time.now.iso8601 }
)
puts updated["name"]
delete — Delete by ID
deleted = client.table("users").delete("550e8400-e29b-41d4-a716-446655440000")
Query Builder
All query builder methods return self and are fully chainable. Call get at the end to execute.
select — Choose Columns
# Specific columns
client.table("users").select("id", "email", "name").get
# All columns (default)
client.table("users").get
Filtering
Available operators
| Method | PostgREST operator | Description |
|---|---|---|
eq(col, val) |
eq |
Equals |
neq(col, val) |
neq |
Not equals |
gt(col, val) |
gt |
Greater than |
gte(col, val) |
gte |
Greater than or equal |
lt(col, val) |
lt |
Less than |
lte(col, val) |
lte |
Less than or equal |
like(col, pat) |
like |
SQL LIKE pattern |
ilike(col, pat) |
ilike |
Case-insensitive LIKE |
is_null(col) |
is.null |
Column is NULL |
is_not_null(col) |
not.is.null |
Column is not NULL |
in_list(col, arr) |
in.(...) |
Column in list |
not_in(col, arr) |
not.in.(...) |
Column not in list |
between(col, min, max) |
gte+lte |
Inclusive range |
not_between(col, min, max) |
lt+gt |
Outside range |
filter(col, op, val) |
any above | Generic filter |
or_filter(col, op, val) |
OR | OR condition |
# Chained filters (all AND by default)
result = client.table("orders")
.gte("total", 100)
.lte("total", 500)
.eq("status", "shipped")
.get
# LIKE / ILIKE
result = client.table("products")
.ilike("name", "%widget%")
.get
# IN list
result = client.table("users")
.in_list("role", ["admin", "manager"])
.get
# NULL check
result = client.table("users").is_null("deleted_at").get
# Date range
result = client.table("orders")
.between("created_at", "2025-01-01", "2025-12-31")
.get
order_by — Sort Results
# Single column
client.table("products").order_by("price", "asc").get
client.table("products").order_by("created_at", "desc").get
# Multiple columns
client.table("products")
.order_by("category", "asc")
.order_by("price", "desc")
.get
group_by — Aggregate Groups
result = client.table("orders")
.select("status", "sum(total)", "count(*)")
.group_by("status")
.get
limit / offset — Pagination
result = client.table("products").limit(20).offset(40).get
paginate — Page-Based Pagination
result = client.table("products").paginate(page: 3, per_page: 20)
puts result["data"] # Array of records
puts result["page"] # 3
puts result["per_page"] # 20
puts result["total"] # Total records matching filters
puts result["total_pages"] # Total pages
first — Single Record
user = client.table("users").eq("email", "alice@example.com").first
puts user["name"] # nil if not found
single — Exactly One Record
begin
user = client.table("users").eq("email", "alice@example.com").single
rescue WOWSQL::WOWSQLError => e
puts "Not found or multiple records: #{e.}"
end
count — Total Count
total = client.table("users").eq("is_active", true).count
puts "Active users: #{total}"
sum / avg — Aggregates
total_revenue = client.table("orders").eq("status", "completed").sum("total")
avg_price = client.table("products").eq("category", "electronics").avg("price")
puts "Revenue: #{total_revenue}"
puts "Average price: #{avg_price}"
Authentication
auth = WOWSQL::ProjectAuthClient.new(
"myproject",
"wowsql_anon_...",
base_domain: "wowsqlconnect.com",
secure: true,
timeout: 30,
verify_ssl: true,
token_storage: nil # Optional custom token storage (implements TokenStorage)
)
sign_up
response = auth.sign_up(
email: "alice@example.com",
password: "SecurePass123!",
full_name: "Alice Smith",
user_metadata: { plan: "pro" }
)
puts response.session.access_token
puts response.user.id
puts response.user.email
sign_in
response = auth.sign_in(
email: "alice@example.com",
password: "SecurePass123!"
)
puts response.session.access_token
puts response.session.refresh_token
get_user
user = auth.get_user
puts user.id
puts user.email
puts user.full_name
puts user.email_verified
puts user..inspect
OAuth — Google, GitHub, etc.
Step 1: Get the authorization URL
oauth = auth.(
provider: "google",
redirect_uri: "https://myapp.com/auth/callback"
)
# Redirect the browser to:
puts oauth["authorization_url"]
Step 2: Exchange the callback code
result = auth.exchange_oauth_callback(
provider: "google",
code: params[:code],
redirect_uri: "https://myapp.com/auth/callback"
)
puts result.session.access_token
puts result.user.email
forgot_password / reset_password
# Send reset email
auth.forgot_password(email: "alice@example.com")
# Reset with token from email
auth.reset_password(
token: "reset_token_from_email",
new_password: "NewSecurePass456!"
)
send_otp / verify_otp
# Send OTP
auth.send_otp(email: "alice@example.com", purpose: "login")
# Verify OTP
response = auth.verify_otp(
email: "alice@example.com",
otp: "123456",
purpose: "login"
)
puts response.session.access_token
Purposes: "login", "signup", "password_reset".
send_magic_link
auth.send_magic_link(email: "alice@example.com", purpose: "login")
Purposes: "login", "signup", "email_verification".
verify_email / resend_verification
# Verify email from link token
result = auth.verify_email(token: "verification_token_from_email")
puts result["success"]
# Resend if needed
auth.resend_verification(email: "alice@example.com")
refresh_token
response = auth.refresh_token
puts response.session.access_token
change_password / update_user
# Change password
auth.change_password(
current_password: "OldPass123!",
new_password: "NewPass456!"
)
# Update profile
user = auth.update_user(
full_name: "Alice Smith",
avatar_url: "https://cdn.example.com/avatar.jpg",
user_metadata: { bio: "Developer" }
)
logout
auth.logout
File Storage
storage = WOWSQL::WOWSQLStorage.new(
"myproject",
"wowsql_anon_...",
base_domain: "wowsqlconnect.com",
secure: true,
timeout: 60,
verify_ssl: true
)
create_bucket
bucket = storage.create_bucket(
"avatars",
public: true,
file_size_limit: 5 * 1024 * 1024, # 5 MB
allowed_mime_types: ["image/jpeg", "image/png"]
)
puts bucket.name
puts bucket.public
upload / upload_from_path
# Upload from IO
File.open("photo.jpg", "rb") do |f|
file = storage.upload("avatars", f, path: "users/alice.jpg")
puts file.path
puts file.size
end
# Upload from filesystem path
file = storage.upload_from_path(
"/local/path/photo.jpg",
bucket_name: "avatars",
path: "users/alice.jpg"
)
list_files / download / delete_file
# List files
files = storage.list_files("avatars", prefix: "users/", limit: 50)
files.each { |f| puts "#{f.path} (#{f.size_mb.round(2)} MB)" }
# Download to memory
content = storage.download("avatars", "users/alice.jpg")
# Download to disk
storage.download_to_file("avatars", "users/alice.jpg", "/local/alice.jpg")
# Delete
storage.delete_file("avatars", "users/alice.jpg")
get_public_url
url = storage.get_public_url("avatars", "users/alice.jpg")
puts url # https://myproject.wowsqlconnect.com/api/v1/storage/...
Schema Management
Schema operations require a service role key (wowsql_service_...).
schema = WOWSQL::WOWSQLSchema.new(
"myproject",
"wowsql_service_...",
base_domain: "wowsqlconnect.com",
secure: true
)
create_table
schema.create_table(
"products",
[
{ "name" => "id", "type" => "UUID", "auto_increment" => true },
{ "name" => "name", "type" => "VARCHAR(255)", "nullable" => false },
{ "name" => "price", "type" => "DECIMAL(10,2)", "nullable" => false },
{ "name" => "category", "type" => "VARCHAR(100)" },
{ "name" => "metadata", "type" => "JSONB", "default" => "'{}'" },
{ "name" => "created_at", "type" => "TIMESTAMPTZ", "default" => "CURRENT_TIMESTAMP" }
],
primary_key: "id",
indexes: ["category", "name"]
)
add_column / drop_column / rename_column
# Add
schema.add_column("products", "sku", "VARCHAR(50)", nullable: true)
# Drop
schema.drop_column("products", "old_field")
# Rename
schema.rename_column("products", "sku", "product_sku")
# Modify type / nullability / default
schema.modify_column("products", "price", column_type: "NUMERIC(12,2)", nullable: false)
drop_table / execute_sql
# Drop table (irreversible)
schema.drop_table("products", cascade: false)
# Execute raw DDL SQL
schema.execute_sql("CREATE INDEX idx_products_category ON products (category)")
schema.execute_sql("ALTER TABLE users ADD COLUMN last_login TIMESTAMPTZ")
Error Handling
All SDK errors are subclasses of WOWSQL::WOWSQLError.
begin
result = client.table("orders").get_by_id("some-id")
rescue WOWSQL::WOWSQLError => e
puts e. # Human-readable error message
puts e.status_code # HTTP status code (e.g., 400, 401, 403, 404, 500)
puts e.response # Raw response body hash
end
Storage-specific errors:
begin
storage.upload("avatars", large_file, path: "big.mov")
rescue WOWSQL::StorageLimitExceededError => e
puts "File too large: #{e.}"
rescue WOWSQL::StorageError => e
puts "Storage error: #{e.}"
end
Schema-specific errors:
begin
schema.drop_table("important_table")
rescue WOWSQL::SchemaPermissionError => e
puts "Permission denied — use a service role key"
rescue WOWSQL::WOWSQLError => e
puts "Schema error: #{e.}"
end
Response Format
All get and query builder calls return a consistent hash:
{
"data" => [...], # Array of record hashes
"count" => 10, # Number of records in this response
"total" => 120, # Total matching records (from Content-Range)
"limit" => 20, # Applied limit
"offset" => 0 # Applied offset
}
Single-record operations (create, update, delete, get_by_id, upsert) return a plain Hash representing the record.
Pagination (paginate) returns:
{
"data" => [...],
"page" => 2,
"per_page" => 20,
"total" => 120,
"total_pages" => 6
}
License
MIT License — see LICENSE for details.