0
# Ruby API Reference
1
2
The FoundationDB Ruby API provides a Ruby-native interface to the database with block-based transaction handling, enumerable range iteration, and Ruby idioms for database operations. The API follows Ruby conventions and integrates well with Ruby applications.
3
4
## Installation
5
6
```bash
7
gem install fdb
8
```
9
10
## Core Requires
11
12
```ruby
13
require 'fdb'
14
require 'fdb/tuple'
15
require 'fdb/subspace'
16
require 'fdb/directory'
17
```
18
19
## Capabilities
20
21
### Initialization and Database Connection
22
23
```ruby { .api }
24
module FDB
25
# Set API version (must be called first)
26
def self.api_version(version)
27
28
# Open database connection
29
def self.open(cluster_file = nil)
30
31
# Execute block with automatic transaction retry
32
def self.transactional(db_or_tr = nil, &block)
33
end
34
```
35
36
**Usage Example:**
37
38
```ruby
39
require 'fdb'
40
41
# Initialize FoundationDB
42
FDB.api_version(740)
43
44
# Open database
45
db = FDB.open # Uses default cluster file
46
# or specify cluster file path
47
db = FDB.open('/etc/foundationdb/fdb.cluster')
48
```
49
50
### Database Operations
51
52
```ruby { .api }
53
class FDB::Database
54
# Create new transaction
55
def create_transaction
56
57
# Execute block with automatic retry
58
def transact(&block)
59
60
# Open tenant for multi-tenancy
61
def open_tenant(tenant_name)
62
end
63
```
64
65
### Transaction Operations
66
67
```ruby { .api }
68
class FDB::Transaction
69
# Read single key
70
def get(key, snapshot: false)
71
72
# Read key range (returns Enumerator)
73
def get_range(begin_key, end_key, options = {})
74
75
# Write key-value pair
76
def set(key, value)
77
78
# Alias for set (Ruby idiom)
79
def []=(key, value)
80
81
# Read single key (Ruby idiom)
82
def [](key, snapshot: false)
83
84
# Delete single key
85
def clear(key)
86
87
# Delete key range
88
def clear_range(begin_key, end_key)
89
90
# Commit transaction
91
def commit
92
93
# Handle errors and retries
94
def on_error(error)
95
96
# Get/set read version
97
def get_read_version
98
def set_read_version(version)
99
100
# Get committed version (after commit)
101
def get_committed_version
102
103
# Get versionstamp (after commit)
104
def get_versionstamp
105
end
106
```
107
108
**Usage Example:**
109
110
```ruby
111
# Functional transaction handling
112
result = FDB.transactional do |tr|
113
value = tr.get('my_key')
114
if value.nil?
115
tr.set('my_key', 'initial_value')
116
'created'
117
else
118
value
119
end
120
end
121
122
# Manual transaction handling with Ruby idioms
123
db.transact do |tr|
124
tr['counter'] = '1' # Same as tr.set('counter', '1')
125
value = tr['counter'] # Same as tr.get('counter')
126
end
127
```
128
129
### Range Operations and Iteration
130
131
```ruby { .api }
132
# Range iteration with Ruby blocks
133
tr.get_range('begin_key', 'end_key').each do |kv|
134
puts "#{kv.key}: #{kv.value}"
135
end
136
137
# Range options
138
options = {
139
limit: 100,
140
reverse: false,
141
streaming_mode: FDB::StreamingMode::ITERATOR
142
}
143
144
# Enumerable methods work on ranges
145
user_data = tr.get_range(user_begin, user_end)
146
.select { |kv| kv.key.include?('active') }
147
.map { |kv| JSON.parse(kv.value) }
148
```
149
150
### Key Selectors
151
152
```ruby { .api }
153
class FDB::KeySelector
154
def initialize(key, or_equal, offset)
155
156
def self.last_less_than(key)
157
def self.last_less_or_equal(key)
158
def self.first_greater_than(key)
159
def self.first_greater_or_equal(key)
160
161
attr_reader :key, :or_equal, :offset
162
end
163
164
class FDB::KeyValue
165
attr_reader :key, :value
166
end
167
```
168
169
### Future Operations
170
171
```ruby { .api }
172
class FDB::Future
173
# Block until result is ready
174
def wait
175
176
# Check if ready without blocking
177
def ready?
178
179
# Set callback for completion
180
def on_ready(&block)
181
end
182
```
183
184
### Tenant Operations
185
186
```ruby { .api }
187
class FDB::Tenant
188
# Create transaction within tenant keyspace
189
def create_transaction
190
191
# Execute block with automatic retry
192
def transact(&block)
193
194
# Get tenant name
195
def name
196
end
197
```
198
199
### Error Handling
200
201
```ruby { .api }
202
class FDB::Error < StandardError
203
attr_reader :code
204
205
def initialize(code)
206
@code = code
207
super(FDB.get_error(code))
208
end
209
210
# Error predicate methods
211
def retryable?
212
def maybe_committed?
213
def retryable_not_committed?
214
end
215
```
216
217
### Tuple Encoding
218
219
```ruby { .api }
220
module FDB::Tuple
221
# Encode tuple to bytes
222
def self.pack(items)
223
224
# Decode bytes to tuple
225
def self.unpack(data)
226
227
# Create range for tuple prefix
228
def self.range(prefix)
229
end
230
```
231
232
**Usage Example:**
233
234
```ruby
235
require 'fdb/tuple'
236
237
# Encode structured keys
238
user_key = FDB::Tuple.pack(['users', user_id, 'profile'])
239
score_key = FDB::Tuple.pack(['scores', game_id, timestamp, player_id])
240
241
# Create ranges for prefix queries
242
user_range = FDB::Tuple.range(['users', user_id])
243
begin_key, end_key = user_range
244
245
# Use in transactions
246
FDB.transactional do |tr|
247
tr.get_range(begin_key, end_key).each do |kv|
248
key_parts = FDB::Tuple.unpack(kv.key)
249
# Process user data
250
end
251
end
252
```
253
254
### Subspace Operations
255
256
```ruby { .api }
257
class FDB::Subspace
258
def initialize(prefix_tuple = [], raw_prefix = '')
259
260
# Pack tuple with subspace prefix
261
def pack(tuple = [])
262
263
# Unpack key and remove prefix
264
def unpack(key)
265
266
# Create range within subspace
267
def range(tuple = [])
268
269
# Check if key belongs to subspace
270
def contains?(key)
271
272
# Create child subspace
273
def subspace(tuple)
274
275
# Get raw prefix
276
def key
277
end
278
```
279
280
### Directory Layer
281
282
```ruby { .api }
283
module FDB::Directory
284
# Create main directory layer
285
def self.new
286
287
class DirectoryLayer
288
# Create or open directory
289
def create_or_open(tr, path, layer = '')
290
291
# Open existing directory
292
def open(tr, path, layer = '')
293
294
# Create new directory
295
def create(tr, path, layer = '', prefix = nil)
296
297
# List subdirectories
298
def list(tr, path = [])
299
300
# Remove directory
301
def remove(tr, path)
302
303
# Move directory
304
def move(tr, old_path, new_path)
305
306
# Check if directory exists
307
def exists?(tr, path)
308
end
309
310
class Directory < FDB::Subspace
311
# Get layer identifier
312
def layer
313
314
# Get directory path
315
def path
316
end
317
end
318
319
# Default directory layer
320
FDB.directory = FDB::Directory.new
321
```
322
323
### Locality Information
324
325
```ruby { .api }
326
module FDB::Locality
327
# Get shard boundary keys
328
def self.get_boundary_keys(tr, begin_key, end_key)
329
330
# Get replica addresses for key
331
def self.get_addresses_for_key(tr, key)
332
end
333
```
334
335
## Configuration Options
336
337
### Transaction Options
338
339
```ruby { .api }
340
module FDB::TransactionOption
341
CAUSAL_READ_RISKY = 20
342
READ_YOUR_WRITES_DISABLE = 22
343
PRIORITY_SYSTEM_IMMEDIATE = 200
344
PRIORITY_BATCH = 201
345
TIMEOUT = 500
346
RETRY_LIMIT = 501
347
SIZE_LIMIT = 503
348
end
349
350
# Set transaction options
351
tr.options.set_timeout(5000) # 5 seconds
352
tr.options.set_retry_limit(100)
353
```
354
355
### Streaming Modes
356
357
```ruby { .api }
358
module FDB::StreamingMode
359
WANT_ALL = -2
360
ITERATOR = -1 # Default
361
EXACT = 0
362
SMALL = 1
363
MEDIUM = 2
364
LARGE = 3
365
SERIAL = 4
366
end
367
```
368
369
## Complete Usage Example
370
371
```ruby
372
require 'fdb'
373
require 'fdb/tuple'
374
require 'fdb/subspace'
375
require 'fdb/directory'
376
require 'json'
377
require 'time'
378
379
# Initialize FoundationDB
380
FDB.api_version(740)
381
db = FDB.open
382
383
# Set up directory structure
384
users_dir = nil
385
sessions_dir = nil
386
387
FDB.transactional do |tr|
388
root = FDB.directory.create_or_open(tr, ['myapp'])
389
users_dir = root.create_or_open(tr, ['users'])
390
sessions_dir = root.create_or_open(tr, ['sessions'])
391
end
392
393
class UserManager
394
def initialize(db, users_directory)
395
@db = db
396
@users_dir = users_directory
397
end
398
399
def create_user(user_id, email, name)
400
FDB.transactional do |tr|
401
user_space = @users_dir.subspace([user_id])
402
403
# Store user profile using Ruby idioms
404
tr[user_space.pack(['email'])] = email
405
tr[user_space.pack(['name'])] = name
406
tr[user_space.pack(['created'])] = Time.now.to_f.to_s
407
408
# Add to email index using tuple encoding
409
email_key = FDB::Tuple.pack(['email_index', email, user_id])
410
tr[email_key] = ''
411
412
user_id
413
end
414
end
415
416
def get_user_profile(user_id)
417
@db.transact do |tr|
418
user_space = @users_dir.subspace([user_id])
419
begin_key, end_key = user_space.range
420
421
profile = {}
422
tr.get_range(begin_key, end_key).each do |kv|
423
field = user_space.unpack(kv.key)[0]
424
profile[field] = kv.value
425
end
426
427
profile
428
end
429
end
430
431
def find_user_by_email(email)
432
@db.transact do |tr|
433
begin_key, end_key = FDB::Tuple.range(['email_index', email])
434
435
tr.get_range(begin_key, end_key, limit: 1).first&.then do |kv|
436
# Extract user_id from key
437
FDB::Tuple.unpack(kv.key)[2]
438
end
439
end
440
end
441
442
def get_all_users
443
@db.transact do |tr|
444
begin_key, end_key = @users_dir.range
445
446
users = {}
447
tr.get_range(begin_key, end_key).each do |kv|
448
key_parts = @users_dir.unpack(kv.key)
449
user_id, field = key_parts
450
451
users[user_id] ||= {}
452
users[user_id][field] = kv.value
453
end
454
455
users
456
end
457
end
458
459
def update_user_field(user_id, field, value)
460
FDB.transactional do |tr|
461
user_space = @users_dir.subspace([user_id])
462
463
# Check if user exists
464
existing = tr[user_space.pack(['email'])]
465
raise "User not found" if existing.nil?
466
467
# Update field
468
tr[user_space.pack([field])] = value
469
470
# Update modification time
471
tr[user_space.pack(['modified'])] = Time.now.to_f.to_s
472
473
value
474
end
475
end
476
477
def delete_user(user_id)
478
FDB.transactional do |tr|
479
user_space = @users_dir.subspace([user_id])
480
481
# Get email for index cleanup
482
email = tr[user_space.pack(['email'])]
483
return false if email.nil?
484
485
# Remove from email index
486
email_key = FDB::Tuple.pack(['email_index', email, user_id])
487
tr.clear(email_key)
488
489
# Remove all user data
490
begin_key, end_key = user_space.range
491
tr.clear_range(begin_key, end_key)
492
493
true
494
end
495
end
496
end
497
498
# Usage example
499
user_manager = UserManager.new(db, users_dir)
500
501
begin
502
# Create user
503
user_id = user_manager.create_user('user123', 'alice@example.com', 'Alice Smith')
504
puts "Created user: #{user_id}"
505
506
# Get user profile
507
profile = user_manager.get_user_profile(user_id)
508
puts "User profile: #{profile}"
509
510
# Update user
511
user_manager.update_user_field(user_id, 'last_login', Time.now.to_f.to_s)
512
puts "Updated user last_login"
513
514
# Find user by email
515
found_user = user_manager.find_user_by_email('alice@example.com')
516
puts "Found user by email: #{found_user}"
517
518
# Get all users
519
all_users = user_manager.get_all_users
520
puts "All users: #{all_users.keys}"
521
522
# Advanced range query with Ruby enumerable methods
523
recent_users = FDB.transactional do |tr|
524
begin_key, end_key = users_dir.range
525
526
cutoff_time = (Time.now - 86400).to_f # 24 hours ago
527
528
tr.get_range(begin_key, end_key)
529
.select { |kv| users_dir.unpack(kv.key)[1] == 'created' }
530
.select { |kv| kv.value.to_f > cutoff_time }
531
.map { |kv| users_dir.unpack(kv.key)[0] }
532
.uniq
533
end
534
535
puts "Recent users: #{recent_users}"
536
537
rescue FDB::Error => e
538
puts "FoundationDB error #{e.code}: #{e.message}"
539
puts "Retryable: #{e.retryable?}"
540
puts "Maybe committed: #{e.maybe_committed?}"
541
ensure
542
# FoundationDB handles cleanup automatically
543
end
544
```