Skip to content

surrealdb/surrealdb.rb

Repository files navigation

surrealdb.rb

The official SurrealDB SDK for Ruby.

CI Gem Version License

Installation

gem install surrealdb

Or add to your Gemfile:

gem "surrealdb"

Quick Start

require "surrealdb"

SurrealDB.connect("ws://localhost:8000") do |db|
  db.signin("user" => "root", "pass" => "root")
  db.use("test", "test")

  # Create
  db.create("person", { "name" => "Alice", "age" => 30 })

  # Select
  people = db.select("person")

  # Query
  results = db.query("SELECT * FROM person WHERE age > $min", { "min" => 25 })

  # Update
  db.merge("person:alice", { "email" => "alice@example.com" })

  # Delete
  db.delete("person:alice")
end

Connection Types

The SDK supports three transport protocols, selected automatically by URL scheme:

Scheme Transport Live Queries Sessions Requires
ws://, wss:// WebSocket Yes Yes --
http://, https:// HTTP No No --
mem://, surrealkv://, file:// Embedded Yes Yes surrealdb-embedded gem
# WebSocket (recommended for most use cases)
client = SurrealDB::Client.new("ws://localhost:8000")

# HTTP (stateless, simpler)
client = SurrealDB::Client.new("http://localhost:8000")

# Embedded (in-process, no server needed)
require "surrealdb/embedded"
client = SurrealDB::Client.new("mem://")

Authentication

SurrealDB.connect("ws://localhost:8000") do |db|
  # Root authentication
  db.signin("user" => "root", "pass" => "root")

  # Namespace authentication
  db.signin("user" => "ns_user", "pass" => "ns_pass", "ns" => "my_namespace")

  # Database authentication
  db.signin("user" => "db_user", "pass" => "db_pass", "ns" => "my_namespace", "db" => "my_database")

  # Record user authentication
  token = db.signup(
    "ns" => "my_namespace",
    "db" => "my_database",
    "ac" => "user_access",
    "username" => "alice",
    "password" => "password123"
  )

  # Token authentication
  db.authenticate(token)

  # Invalidate session
  db.invalidate
end

CRUD Operations

SurrealDB.connect("ws://localhost:8000") do |db|
  db.signin("user" => "root", "pass" => "root")
  db.use("test", "test")

  # Create a record (auto-generated ID)
  person = db.create("person", { "name" => "Alice", "age" => 30 })

  # Create with specific ID
  db.create("person:bob", { "name" => "Bob", "age" => 25 })

  # Select all records from a table
  people = db.select("person")

  # Select a specific record
  alice = db.select("person:alice")

  # Insert multiple records
  db.insert("person", [
    { "name" => "Charlie", "age" => 35 },
    { "name" => "Diana", "age" => 28 }
  ])

  # Update (full replace)
  db.update("person:bob", { "name" => "Bob", "age" => 26, "email" => "bob@example.com" })

  # Upsert (insert or update)
  db.upsert("person:eve", { "name" => "Eve", "age" => 22 })

  # Merge (partial update)
  db.merge("person:bob", { "email" => "bob@newmail.com" })

  # Patch (JSON Patch)
  db.patch("person:bob", [
    { "op" => "replace", "path" => "/age", "value" => 27 }
  ])

  # Delete a record
  db.delete("person:bob")

  # Delete all records from a table
  db.delete("person")

  # Create a relation
  db.relate("person:alice", "knows", "person:bob", { "since" => 2024 })
end

Queries

SurrealDB.connect("ws://localhost:8000") do |db|
  db.signin("user" => "root", "pass" => "root")
  db.use("test", "test")

  # Simple query
  results = db.query("SELECT * FROM person")

  # Parameterized query
  results = db.query(
    "SELECT * FROM person WHERE age > $min_age AND name = $name",
    { "min_age" => 25, "name" => "Alice" }
  )

  # Multi-statement query
  results = db.query(<<~SQL)
    CREATE person:alice SET name = 'Alice', age = 30;
    CREATE person:bob SET name = 'Bob', age = 25;
    SELECT * FROM person;
  SQL

  # Connection-scoped variables
  db.set("current_user", "alice")
  results = db.query("SELECT * FROM person WHERE name = $current_user")
  db.unset("current_user")

  # Run a SurrealDB function
  result = db.run("fn::my_function", "arg1", "arg2")
end

Structured Query Results

Use query_raw to get per-statement metadata (status, timing, errors) for multi-statement queries:

results = db.query_raw("CREATE person:a SET name = 'Alice'; SELECT * FROM missing_table;")
results.each do |qr|
  if qr.ok?
    puts "#{qr.time}: #{qr.result}"
  else
    puts "Error: #{qr.error}"
  end
end

Embedded Database

For in-process usage without a separate server, install the embedded gem:

gem install surrealdb-embedded

Then opt in with an extra require:

require "surrealdb"
require "surrealdb/embedded"

SurrealDB.connect("mem://") do |db|
  db.use("test", "test")
  db.create("person", { "name" => "Alice" })
end

Supported schemes: mem:// (in-memory), surrealkv://path (persistent), file://path (file-based).

Requires libsurrealdb_c installed on the system or pointed to via SURREALDB_LIB_PATH.

Automatic Reconnection

Enable automatic WebSocket reconnection with exponential backoff:

client = SurrealDB::Client.new("ws://localhost:8000",
  reconnect: true,
  reconnect_max_retries: 5,
  reconnect_delay: 1.0
)
client.connect
client.signin("user" => "root", "pass" => "root")
client.use("test", "test")

# If the connection drops, the SDK will automatically reconnect,
# replay use/signin/let state, and retry the failed request.
client.query("SELECT * FROM person")

Live Queries

Live queries are supported over WebSocket and embedded connections.

SurrealDB.connect("ws://localhost:8000") do |db|
  db.signin("user" => "root", "pass" => "root")
  db.use("test", "test")

  # Start a live query
  live_id = db.live("person")

  # Subscribe to notifications
  db.subscribe(live_id) do |notification|
    puts "Action: #{notification['action']}"
    puts "Result: #{notification['result']}"
  end

  # Changes to the table will trigger notifications
  db.create("person", { "name" => "Alice" })

  # Stop the live query
  db.kill(live_id)
end

SurrealDB Types

The SDK provides Ruby types that map to SurrealDB's type system:

# RecordID
rid = SurrealDB::RecordID.new("person", "alice")
rid = SurrealDB::RecordID.parse("person:alice")
rid.table  # => "person"
rid.id     # => "alice"

# Table
table = SurrealDB::Table.new("person")

# Duration
duration = SurrealDB::Duration.parse("1h30m")
duration.secs   # => 5400
duration.to_f   # => 5400.0

# None (distinct from nil/NULL)
SurrealDB::NONE

# Geometry
point = SurrealDB::GeometryPoint.new(-122.4194, 37.7749)
line = SurrealDB::GeometryLine.new(point1, point2)
polygon = SurrealDB::GeometryPolygon.new(exterior_ring)

# Range
range = SurrealDB::Range.new(
  SurrealDB::BoundIncluded.new(1),
  SurrealDB::BoundExcluded.new(10)
)

Error Handling

begin
  db.query("INVALID SYNTAX")
rescue SurrealDB::QueryError => e
  puts "Query failed: #{e.message}"
rescue SurrealDB::NotFoundError => e
  puts "Not found: #{e.message}"
rescue SurrealDB::NotAllowedError => e
  puts "Permission denied: #{e.message}"
rescue SurrealDB::ServerError => e
  # Catch-all for server errors
  puts "Server error (#{e.kind}): #{e.message}"
  puts "Cause: #{e.server_cause.message}" if e.server_cause
rescue SurrealDB::ConnectionError => e
  puts "Connection lost: #{e.message}"
rescue SurrealDB::TimeoutError => e
  puts "Request timed out: #{e.message}"
end

Configuration

SurrealDB.configure do |config|
  config.timeout = 60  # seconds (default: 30)
  config.logger = Logger.new($stdout)
end

# Per-connection timeout
client = SurrealDB::Client.new("ws://localhost:8000", timeout: 10)

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Run the tests (bundle exec rspec)
  4. Run the linter (bundle exec rubocop)
  5. Commit your changes
  6. Push to the branch
  7. Create a Pull Request

Running Integration Tests

Integration tests require a running SurrealDB instance:

docker run --rm -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass root --allow-all
bundle exec rspec spec/integration

Links

About

SurrealDB Ruby SDK

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages