关于 appwrite-ruby
appwrite-ruby 是用于配置 Appwrite 客户端并操作其服务的 Ruby SDK,提供身份验证、用户、表、文件存储、团队和无服务器函数等示例。
主要功能
- 01配置 Appwrite 客户端与凭据
- 02管理用户和身份验证会话
- 03创建并查询数据库表与行
- 04上传和列出存储文件
- 05管理团队、角色与权限
- 06执行并查看无服务器函数
适用场景
- 01为 Ruby 应用添加 Appwrite 身份验证
- 02使用 Ruby 代码管理表行
- 03上传文件并设置访问权限
- 04运行 Appwrite 无服务器函数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ appwrite/ skills --skill appwrite-ruby安装后在 Claude Code 中输入/appwrite-ruby确认已加载。
详细介绍
Appwrite Ruby SDK
安装
gem install appwrite
设置客户端
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project(ENV['APPWRITE_PROJECT_ID'])
.set_key(ENV['APPWRITE_API_KEY'])
代码示例
用户管理
users = Users.new(client)
# Create user
user = users.create(user_id: ID.unique, email: '[email protected]', password: 'password123', name: 'User Name')
# List users
list = users.list(queries: [Query.limit(25)])
# Get user
fetched = users.get(user_id: '[USER_ID]')
# Delete user
users.delete(user_id: '[USER_ID]')
数据库操作
注意: 所有新代码都应使用
TablesDB(而不是已弃用的Databases类)。仅当现有代码库已依赖Databases,或用户明确要求时,才使用Databases。提示: SDK 方法调用应优先使用关键字参数(例如
database_id: '...')。仅当现有代码库已使用位置参数,或用户明确要求时,才使用位置参数。
tables_db = TablesDB.new(client)
# Create database
db = tables_db.create(database_id: ID.unique, name: 'My Database')
# Create row
doc = tables_db.create_row(
database_id: '[DATABASE_ID]',
table_id: '[TABLE_ID]',
row_id: ID.unique,
data: { title: 'Hello World' }
)
# Query rows
results = tables_db.list_rows(
database_id: '[DATABASE_ID]',
table_id: '[TABLE_ID]',
queries: [Query.equal('title', 'Hello World'), Query.limit(10)]
)
# Get row
row = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')
# Update row
tables_db.update_row(
database_id: '[DATABASE_ID]',
table_id: '[TABLE_ID]',
row_id: '[ROW_ID]',
data: { title: 'Updated' }
)
# Delete row
tables_db.delete_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')
字符串列类型
注意: 旧版
string类型已弃用。所有新列都应使用明确的列类型。
| 类型 | 最大字符数 | 索引 | 存储 |
|---|---|---|---|
varchar | 16,383 | 完整索引(如果大小 ≤ 768) | 行内 |
text | 16,383 | 仅前缀 | 页外 |
mediumtext | 4,194,303 | 仅前缀 | 页外 |
longtext | 1,073,741,823 | 仅前缀 | 页外 |
varchar存储在行内,并计入 64 KB 的行大小限制。适合名称、slug 或标识符等较短且需要建立索引的字段。text、mediumtext和longtext存储在页外(行内仅保留一个 20 字节的指针),因此不会占用行大小额度。这些类型不需要size。
# Create table with explicit string column types
tables_db.create_table(
database_id: '[DATABASE_ID]',
table_id: ID.unique,
name: 'articles',
columns: [
{ key: 'title', type: 'varchar', size: 255, required: true }, # inline, fully indexable
{ key: 'summary', type: 'text', required: false }, # off-page, prefix index only
{ key: 'body', type: 'mediumtext', required: false }, # up to ~4 M chars
{ key: 'raw_data', type: 'longtext', required: false }, # up to ~1 B chars
]
)
查询方法
# Filtering
Query.equal('field', 'value') # == (or pass array for IN)
Query.not_equal('field', 'value') # !=
Query.less_than('field', 100) # <
Query.less_than_equal('field', 100) # <=
Query.greater_than('field', 100) # >
Query.greater_than_equal('field', 100) # >=
Query.between('field', 1, 100) # 1 <= field <= 100
Query.is_null('field') # is null
Query.is_not_null('field') # is not null
Query.starts_with('field', 'prefix') # starts with
Query.ends_with('field', 'suffix') # ends with
Query.contains('field', 'sub') # contains
Query.search('field', 'keywords') # full-text search (requires index)
# Sorting
Query.order_asc('field')
Query.order_desc('field')
# Pagination
Query.limit(25) # max rows (default 25, max 100)
Query.offset(0) # skip N rows
Query.cursor_after('[ROW_ID]') # cursor pagination (preferred)
Query.cursor_before('[ROW_ID]')
# Selection & Logic
Query.select(['field1', 'field2']) # return only specified fields
Query.or([Query.equal('a', 1), Query.equal('b', 2)]) # OR
Query.and([Query.greater_than('age', 18), Query.less_than('age', 65)]) # AND (default)
Query.relationship('comments', [Query.equal('approved', true), Query.limit(25)]) # related documents
# Aggregations (optional alias)
Query.count('*', 'total')
Query.sum('amount', 'revenue')
Query.avg('score', 'avg_score')
Query.min('price', 'min_price')
Query.max('price', 'max_price')
# Grouping
Query.group_by(['status', 'year'])
Query.having([Query.greater_than('total', 1)])
Query.distinct
# Joins (table id, left key, right key, operator, alias)
Query.join('orders', '$id', 'customerId')
Query.left_join('orders', '$id', 'customerId', '=', 'ord')
Query.left_join('orders', 'ord', [
Query.on('$id', 'customerId'),
Query.equal('ord.status', 'paid'),
])
# Fluent builder. Pass build() into list methods.
queries = Query.builder
.equal('status', 'published')
.left_join('orders', '$id', 'customerId', '=', 'ord')
.count('*', 'total')
.group_by(['status'])
.limit(25)
.build
tables_db.list_rows(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', queries: queries)
文件存储
storage = Storage.new(client)
# Upload file
file = storage.create_file(bucket_id: '[BUCKET_ID]', file_id: ID.unique, file: InputFile.from_path('/path/to/file.png'))
# List files
files = storage.list_files(bucket_id: '[BUCKET_ID]')
# Delete file
storage.delete_file(bucket_id: '[BUCKET_ID]', file_id: '[FILE_ID]')
InputFile 工厂方法
InputFile.from_path('/path/to/file.png') # from filesystem path
InputFile.from_string('Hello world', 'hello.txt') # from string content
团队
teams = Teams.new(client)
# Create team
team = teams.create(team_id: ID.unique, name: 'Engineering')
# List teams
list = teams.list
# Create membership (invite user by email)
membership = teams.create_membership(
team_id: '[TEAM_ID]',
roles: ['editor'],
email: '[email protected]'
)
# List memberships
members = teams.list_memberships(team_id: '[TEAM_ID]')
# Update membership roles
teams.update_membership(team_id: '[TEAM_ID]', membership_id: '[MEMBERSHIP_ID]', roles: ['admin'])
# Delete team
teams.delete(team_id: '[TEAM_ID]')
基于角色的访问: 设置权限时,对所有团队成员使用
Role.team('[TEAM_ID]'),对特定团队角色使用Role.team('[TEAM_ID]', 'editor')。
无服务器函数
functions = Functions.new(client)
# Execute function
execution = functions.create_execution(function_id: '[FUNCTION_ID]', body: '{"key": "value"}')
# List executions
executions = functions.list_executions(function_id: '[FUNCTION_ID]')
编写函数处理程序(Ruby 运行时)
# src/main.rb — Appwrite Function entry point
def main(context)
# context.req.body — raw body (String)
# context.req.body_json — parsed JSON (Hash or nil)
# context.req.headers — headers (Hash)
# context.req.method — HTTP method
# context.req.path — URL path
# context.req.query — query params (Hash)
context.log("Processing: #{context.req.method} #{context.req.path}")
if context.req.method == 'GET'
return context.res.json({ message: 'Hello from Appwrite Function!' })
end
context.res.json({ success: true }) # JSON
# context.res.text('Hello') # plain text
# context.res.empty # 204
# context.res.redirect('https://...') # 302
end
服务器端渲染(SSR)身份验证
使用 Ruby 框架(Rails、Sinatra 等)的 SSR 应用通过服务端 SDK处理身份验证。你需要两个客户端:
- 管理员客户端 — 使用 API 密钥创建会话并绕过速率限制(可复用的单例)
- 会话客户端 — 使用会话 Cookie 代表用户执行操作(每个请求创建一个,切勿共享)
require 'appwrite'
include Appwrite
# Admin client (reusable)
admin_client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_key(ENV['APPWRITE_API_KEY'])
# Session client (create per-request)
session_client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
session = cookies['a_session_[PROJECT_ID]']
session_client.set_session(session) if session
电子邮件/密码登录(Sinatra)
post '/login' do
account = Account.new(admin_client)
session = account.create_email_password_session(
email: params[:email],
password: params[:password]
)
# Cookie name must be a_session_<PROJECT_ID>
response.set_cookie('a_session_[PROJECT_ID]', {
value: session.secret,
httponly: true,
secure: true,
same_site: :strict,
path: '/',
})
content_type :json
{ success: true }.to_json
end
已验证的请求
get '/user' do
session = request.cookies['a_session_[PROJECT_ID]']
halt 401, { error: 'Unauthorized' }.to_json unless session
session_client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_session(session)
account = Account.new(session_client)
user = account.get
content_type :json
user.to_json
end
OAuth2 SSR 流程
# Step 1: Redirect to OAuth provider
get '/oauth' do
account = Account.new(admin_client)
redirect_url = account.create_o_auth2_token(
provider: OAuthProvider::GITHUB,
success: 'https://example.com/oauth/success',
failure: 'https://example.com/oauth/failure'
)
redirect redirect_url
end
# Step 2: Handle callback — exchange token for session
get '/oauth/success' do
account = Account.new(admin_client)
session = account.create_session(
user_id: params[:userId],
secret: params[:secret]
)
response.set_cookie('a_session_[PROJECT_ID]', {
value: session.secret,
httponly: true, secure: true, same_site: :strict, path: '/',
})
content_type :json
{ success: true }.to_json
end
Cookie 安全: 始终使用
httponly、secure和same_site: :strict来防止 XSS。Cookie 名称必须为a_session_<PROJECT_ID>。
转发用户代理: 调用
session_client.set_forwarded_user_agent(request.user_agent),记录最终用户的浏览器信息,以便调试和保障安全。
错误处理
require 'appwrite'
include Appwrite
begin
row = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')
rescue Appwrite::Exception => e
puts e.message # human-readable message
puts e.code # HTTP status code (Integer)
puts e.type # error type (e.g. 'document_not_found')
puts e.response # full response body (Hash)
end
常见错误代码:
| 代码 | 含义 |
|---|---|
401 | 未授权 — 缺少或无效的会话/API 密钥 |
403 | 禁止访问 — 权限不足 |
404 | 未找到 — 资源不存在 |
409 | 冲突 — ID 重复或违反唯一性约束 |
429 | 请求过多 — 请求频率超出限制 |
权限与角色(重要)
Appwrite 使用权限字符串控制对资源的访问。每项权限将一个操作(read、update、delete、create,或授予创建、更新和删除权限的 write)与一个角色目标配对。默认情况下,除非在行/文件级别明确设置权限,或从表/存储桶设置继承权限,否则任何用户都无权访问。权限是使用 Permission 和 Role 辅助工具构建的字符串数组。
# Permission and Role are included in the main require
require 'appwrite'
include Appwrite
带权限的数据库行
doc = tables_db.create_row(
database_id: '[DATABASE_ID]',
table_id: '[TABLE_ID]',
row_id: ID.unique,
data: { title: 'Hello World' },
permissions: [
Permission.read(Role.user('[USER_ID]')), # specific user can read
Permission.update(Role.user('[USER_ID]')), # specific user can update
Permission.read(Role.team('[TEAM_ID]')), # all team members can read
Permission.read(Role.any), # anyone (including guests) can read
]
)
具有权限的文件上传
file = storage.create_file(
bucket_id: '[BUCKET_ID]',
file_id: ID.unique,
file: InputFile.from_path('/path/to/file.png'),
permissions: [
Permission.read(Role.any),
Permission.update(Role.user('[USER_ID]')),
Permission.delete(Role.user('[USER_ID]')),
]
)
何时设置权限: 需要对每项资源进行访问控制时,请设置行级/文件级权限。如果表中的所有行都遵循相同的规则,请在表/存储桶级别配置权限,并将行权限留空。
常见错误:
- 忘记设置权限 — 资源将对所有用户(包括创建者)不可访问
- 将
Role.any与write/update/delete搭配使用 — 允许任何用户(包括未经身份验证的访客)修改或删除资源- 对敏感数据使用
Permission.read(Role.any)— 资源将公开可读