mcpField
A
appwrite-ruby

appwrite-ruby

github.com/appwrite開発28799 回インストール1 週間前に更新
リポジトリ

appwrite-ruby について

appwrite-rubyは、Appwriteクライアントの設定や各種サービスの操作に使うRuby SDKです。認証、ユーザー、テーブル、ファイルストレージ、チーム、サーバーレス関数の利用例を含みます。

主な機能

  1. 01Appwriteクライアントと認証情報の設定
  2. 02ユーザーと認証セッションの管理
  3. 03データベースのテーブルと行の作成・検索
  4. 04ファイルのアップロードと一覧取得
  5. 05チーム、ロール、権限の管理
  6. 06サーバーレス関数の実行と確認

活用シーン

  1. 01RubyアプリへのAppwrite認証の追加
  2. 02Rubyコードによるテーブル行の管理
  3. 03ファイルのアップロードとアクセス権設定
  4. 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 型は非推奨です。すべての新しいカラムには明示的なカラム型を使用してください。

型最大文字数インデックスストレージ
varchar16,383完全インデックス(サイズが 768 以下の場合)行内にインライン
text16,383プレフィックスのみオフページ
mediumtext4,194,303プレフィックスのみオフページ
longtext1,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) — リソースが誰でも読み取り可能になります

コメント