appwrite-ruby について
appwrite-rubyは、Appwriteクライアントの設定や各種サービスの操作に使うRuby SDKです。認証、ユーザー、テーブル、ファイルストレージ、チーム、サーバーレス関数の利用例を含みます。
主な機能
- 01Appwriteクライアントと認証情報の設定
- 02ユーザーと認証セッションの管理
- 03データベースのテーブルと行の作成・検索
- 04ファイルのアップロードと一覧取得
- 05チーム、ロール、権限の管理
- 06サーバーレス関数の実行と確認
活用シーン
- 01RubyアプリへのAppwrite認証の追加
- 02Rubyコードによるテーブル行の管理
- 03ファイルのアップロードとアクセス権設定
- 04Appwriteサーバーレス関数の実行
インストール
コマンドをコピーしてターミナルで実行します。
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]')
データベース操作
Note: すべての新しいコードでは
TablesDB(非推奨のDatabasesクラスではない)を使用してください。Databasesは、既存のコードベースがすでに依存している場合、またはユーザーが明示的に要求した場合にのみ使用してください。Tip: すべての 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]')
文字列カラムの型
Note: レガシーの
string型は非推奨です。すべての新しいカラムには明示的なカラム型を使用してください。
| 型 | 最大文字数 | インデックス | ストレージ |
|---|---|---|---|
varchar | 16,383 | 完全インデックス(サイズが 768 以下の場合) | 行内にインライン |
text | 16,383 | プレフィックスのみ | オフページ |
mediumtext | 4,194,303 | プレフィックスのみ | オフページ |
longtext | 1,073,741,823 | プレフィックスのみ | オフページ |
varcharはインラインで保存され、64 KB の行サイズ制限にカウントされます。名前、スラッグ、識別子などの短くインデックスされたフィールドに適しています。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 を使用します。2 つのクライアントが必要です。
- 管理者クライアント — 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 のセキュリティ: XSS を防ぐために、常に
httponly、secure、same_site: :strictを使用してください。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、または create + update + delete を付与する 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)— リソースが誰でも読み取り可能になります