{"id":13624,"plugin_id":"plugin_asdk_app_6aa2c33323108191b17b9ccf4233b3ce","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:07:50.339Z","digest":"dbfc659a260d0456304f3cfec83170867eacb3301fc86a794d9c371499f492e4","against":null,"payload":{"name":"appwrite-ruby","description":"Appwrite Ruby SDK skill. Use when building server-side Ruby applications with Appwrite, including Rails and Sinatra integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.","included_files":[],"skill_md_contents":"---\r\nname: appwrite-ruby\r\ndescription: Appwrite Ruby SDK skill. Use when building server-side Ruby applications with Appwrite, including Rails and Sinatra integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.\r\n---\r\n\r\n\r\n# Appwrite Ruby SDK\r\n\r\n## Installation\r\n\r\n```bash\r\ngem install appwrite\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n```ruby\r\nrequire 'appwrite'\r\n\r\ninclude Appwrite\r\n\r\nclient = Client.new\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project(ENV['APPWRITE_PROJECT_ID'])\r\n    .set_key(ENV['APPWRITE_API_KEY'])\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```ruby\r\nusers = Users.new(client)\r\n\r\n# Create user\r\nuser = users.create(user_id: ID.unique, email: 'user@example.com', password: 'password123', name: 'User Name')\r\n\r\n# List users\r\nlist = users.list(queries: [Query.limit(25)])\r\n\r\n# Get user\r\nfetched = users.get(user_id: '[USER_ID]')\r\n\r\n# Delete user\r\nusers.delete(user_id: '[USER_ID]')\r\n```\r\n\r\n### Database Operations\r\n\r\n> **Note:** Use `TablesDB` (not the deprecated `Databases` class) for all new code. Only use `Databases` if the existing codebase already relies on it or the user explicitly requests it.\r\n>\r\n> **Tip:** Prefer keyword arguments (e.g., `database_id: '...'`) for all SDK method calls. Only use positional arguments if the existing codebase already uses them or the user explicitly requests it.\r\n\r\n```ruby\r\ntables_db = TablesDB.new(client)\r\n\r\n# Create database\r\ndb = tables_db.create(database_id: ID.unique, name: 'My Database')\r\n\r\n# Create row\r\ndoc = tables_db.create_row(\r\n    database_id: '[DATABASE_ID]',\r\n    table_id: '[TABLE_ID]',\r\n    row_id: ID.unique,\r\n    data: { title: 'Hello World' }\r\n)\r\n\r\n# Query rows\r\nresults = tables_db.list_rows(\r\n    database_id: '[DATABASE_ID]',\r\n    table_id: '[TABLE_ID]',\r\n    queries: [Query.equal('title', 'Hello World'), Query.limit(10)]\r\n)\r\n\r\n# Get row\r\nrow = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')\r\n\r\n# Update row\r\ntables_db.update_row(\r\n    database_id: '[DATABASE_ID]',\r\n    table_id: '[TABLE_ID]',\r\n    row_id: '[ROW_ID]',\r\n    data: { title: 'Updated' }\r\n)\r\n\r\n# Delete row\r\ntables_db.delete_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')\r\n```\r\n\r\n#### String Column Types\r\n\r\n> **Note:** The legacy `string` type is deprecated. Use explicit column types for all new columns.\r\n\r\n| Type | Max characters | Indexing | Storage |\r\n|------|---------------|----------|---------|\r\n| `varchar` | 16,383 | Full index (if size ≤ 768) | Inline in row |\r\n| `text` | 16,383 | Prefix only | Off-page |\r\n| `mediumtext` | 4,194,303 | Prefix only | Off-page |\r\n| `longtext` | 1,073,741,823 | Prefix only | Off-page |\r\n\r\n- `varchar` is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.\r\n- `text`, `mediumtext`, and `longtext` are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget. `size` is not required for these types.\r\n\r\n```ruby\r\n# Create table with explicit string column types\r\ntables_db.create_table(\r\n    database_id: '[DATABASE_ID]',\r\n    table_id: ID.unique,\r\n    name: 'articles',\r\n    columns: [\r\n        { key: 'title',    type: 'varchar',    size: 255, required: true  },  # inline, fully indexable\r\n        { key: 'summary',  type: 'text',                  required: false },  # off-page, prefix index only\r\n        { key: 'body',     type: 'mediumtext',            required: false },  # up to ~4 M chars\r\n        { key: 'raw_data', type: 'longtext',              required: false },  # up to ~1 B chars\r\n    ]\r\n)\r\n```\r\n\r\n### Query Methods\r\n\r\n```ruby\r\n# Filtering\r\nQuery.equal('field', 'value')             # == (or pass array for IN)\r\nQuery.not_equal('field', 'value')         # !=\r\nQuery.less_than('field', 100)             # <\r\nQuery.less_than_equal('field', 100)       # <=\r\nQuery.greater_than('field', 100)          # >\r\nQuery.greater_than_equal('field', 100)    # >=\r\nQuery.between('field', 1, 100)            # 1 <= field <= 100\r\nQuery.is_null('field')                    # is null\r\nQuery.is_not_null('field')                # is not null\r\nQuery.starts_with('field', 'prefix')      # starts with\r\nQuery.ends_with('field', 'suffix')        # ends with\r\nQuery.contains('field', 'sub')            # contains\r\nQuery.search('field', 'keywords')         # full-text search (requires index)\r\n\r\n# Sorting\r\nQuery.order_asc('field')\r\nQuery.order_desc('field')\r\n\r\n# Pagination\r\nQuery.limit(25)                           # max rows (default 25, max 100)\r\nQuery.offset(0)                           # skip N rows\r\nQuery.cursor_after('[ROW_ID]')            # cursor pagination (preferred)\r\nQuery.cursor_before('[ROW_ID]')\r\n\r\n# Selection & Logic\r\nQuery.select(['field1', 'field2'])        # return only specified fields\r\nQuery.or([Query.equal('a', 1), Query.equal('b', 2)])   # OR\r\nQuery.and([Query.greater_than('age', 18), Query.less_than('age', 65)])  # AND (default)\r\n```\r\n\r\n### File Storage\r\n\r\n```ruby\r\nstorage = Storage.new(client)\r\n\r\n# Upload file\r\nfile = storage.create_file(bucket_id: '[BUCKET_ID]', file_id: ID.unique, file: InputFile.from_path('/path/to/file.png'))\r\n\r\n# List files\r\nfiles = storage.list_files(bucket_id: '[BUCKET_ID]')\r\n\r\n# Delete file\r\nstorage.delete_file(bucket_id: '[BUCKET_ID]', file_id: '[FILE_ID]')\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```ruby\r\nInputFile.from_path('/path/to/file.png')             # from filesystem path\r\nInputFile.from_string('Hello world', 'hello.txt')    # from string content\r\n```\r\n\r\n### Teams\r\n\r\n```ruby\r\nteams = Teams.new(client)\r\n\r\n# Create team\r\nteam = teams.create(team_id: ID.unique, name: 'Engineering')\r\n\r\n# List teams\r\nlist = teams.list\r\n\r\n# Create membership (invite user by email)\r\nmembership = teams.create_membership(\r\n    team_id: '[TEAM_ID]',\r\n    roles: ['editor'],\r\n    email: 'user@example.com'\r\n)\r\n\r\n# List memberships\r\nmembers = teams.list_memberships(team_id: '[TEAM_ID]')\r\n\r\n# Update membership roles\r\nteams.update_membership(team_id: '[TEAM_ID]', membership_id: '[MEMBERSHIP_ID]', roles: ['admin'])\r\n\r\n# Delete team\r\nteams.delete(team_id: '[TEAM_ID]')\r\n```\r\n\r\n> **Role-based access:** Use `Role.team('[TEAM_ID]')` for all team members or `Role.team('[TEAM_ID]', 'editor')` for a specific team role when setting permissions.\r\n\r\n### Serverless Functions\r\n\r\n```ruby\r\nfunctions = Functions.new(client)\r\n\r\n# Execute function\r\nexecution = functions.create_execution(function_id: '[FUNCTION_ID]', body: '{\"key\": \"value\"}')\r\n\r\n# List executions\r\nexecutions = functions.list_executions(function_id: '[FUNCTION_ID]')\r\n```\r\n\r\n#### Writing a Function Handler (Ruby runtime)\r\n\r\n```ruby\r\n# src/main.rb — Appwrite Function entry point\r\ndef main(context)\r\n    # context.req.body         — raw body (String)\r\n    # context.req.body_json    — parsed JSON (Hash or nil)\r\n    # context.req.headers      — headers (Hash)\r\n    # context.req.method       — HTTP method\r\n    # context.req.path         — URL path\r\n    # context.req.query        — query params (Hash)\r\n\r\n    context.log(\"Processing: #{context.req.method} #{context.req.path}\")\r\n\r\n    if context.req.method == 'GET'\r\n        return context.res.json({ message: 'Hello from Appwrite Function!' })\r\n    end\r\n\r\n    context.res.json({ success: true })          # JSON\r\n    # context.res.text('Hello')                  # plain text\r\n    # context.res.empty                          # 204\r\n    # context.res.redirect('https://...')         # 302\r\nend\r\n```\r\n\r\n### Server-Side Rendering (SSR) Authentication\r\n\r\nSSR apps using Ruby frameworks (Rails, Sinatra, etc.) use the **server SDK** to handle auth. You need two clients:\r\n\r\n- **Admin client** — uses an API key, creates sessions, bypasses rate limits (reusable singleton)\r\n- **Session client** — uses a session cookie, acts on behalf of a user (create per-request, never share)\r\n\r\n```ruby\r\nrequire 'appwrite'\r\ninclude Appwrite\r\n\r\n# Admin client (reusable)\r\nadmin_client = Client.new\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project('[PROJECT_ID]')\r\n    .set_key(ENV['APPWRITE_API_KEY'])\r\n\r\n# Session client (create per-request)\r\nsession_client = Client.new\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project('[PROJECT_ID]')\r\n\r\nsession = cookies['a_session_[PROJECT_ID]']\r\nsession_client.set_session(session) if session\r\n```\r\n\r\n#### Email/Password Login (Sinatra)\r\n\r\n```ruby\r\npost '/login' do\r\n    account = Account.new(admin_client)\r\n    session = account.create_email_password_session(\r\n        email: params[:email],\r\n        password: params[:password]\r\n    )\r\n\r\n    # Cookie name must be a_session_<PROJECT_ID>\r\n    response.set_cookie('a_session_[PROJECT_ID]', {\r\n        value: session.secret,\r\n        httponly: true,\r\n        secure: true,\r\n        same_site: :strict,\r\n        path: '/',\r\n    })\r\n\r\n    content_type :json\r\n    { success: true }.to_json\r\nend\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```ruby\r\nget '/user' do\r\n    session = request.cookies['a_session_[PROJECT_ID]']\r\n    halt 401, { error: 'Unauthorized' }.to_json unless session\r\n\r\n    session_client = Client.new\r\n        .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n        .set_project('[PROJECT_ID]')\r\n        .set_session(session)\r\n\r\n    account = Account.new(session_client)\r\n    user = account.get\r\n\r\n    content_type :json\r\n    user.to_json\r\nend\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```ruby\r\n# Step 1: Redirect to OAuth provider\r\nget '/oauth' do\r\n    account = Account.new(admin_client)\r\n    redirect_url = account.create_o_auth2_token(\r\n        provider: OAuthProvider::GITHUB,\r\n        success: 'https://example.com/oauth/success',\r\n        failure: 'https://example.com/oauth/failure'\r\n    )\r\n    redirect redirect_url\r\nend\r\n\r\n# Step 2: Handle callback — exchange token for session\r\nget '/oauth/success' do\r\n    account = Account.new(admin_client)\r\n    session = account.create_session(\r\n        user_id: params[:userId],\r\n        secret: params[:secret]\r\n    )\r\n\r\n    response.set_cookie('a_session_[PROJECT_ID]', {\r\n        value: session.secret,\r\n        httponly: true, secure: true, same_site: :strict, path: '/',\r\n    })\r\n\r\n    content_type :json\r\n    { success: true }.to_json\r\nend\r\n```\r\n\r\n> **Cookie security:** Always use `httponly`, `secure`, and `same_site: :strict` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.\r\n\r\n> **Forwarding user agent:** Call `session_client.set_forwarded_user_agent(request.user_agent)` to record the end-user's browser info for debugging and security.\r\n\r\n## Error Handling\r\n\r\n```ruby\r\nrequire 'appwrite'\r\ninclude Appwrite\r\n\r\nbegin\r\n    row = tables_db.get_row(database_id: '[DATABASE_ID]', table_id: '[TABLE_ID]', row_id: '[ROW_ID]')\r\nrescue Appwrite::Exception => e\r\n    puts e.message    # human-readable message\r\n    puts e.code       # HTTP status code (Integer)\r\n    puts e.type       # error type (e.g. 'document_not_found')\r\n    puts e.response   # full response body (Hash)\r\nend\r\n```\r\n\r\n**Common error codes:**\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `401` | Unauthorized — missing or invalid session/API key |\r\n| `403` | Forbidden — insufficient permissions |\r\n| `404` | Not found — resource does not exist |\r\n| `409` | Conflict — duplicate ID or unique constraint |\r\n| `429` | Rate limited — too many requests |\r\n\r\n## Permissions & Roles (Critical)\r\n\r\nAppwrite uses permission strings to control access to resources. Each permission pairs an action (`read`, `update`, `delete`, `create`, or `write` which grants create + update + delete) with a role target. By default, **no user has access** unless permissions are explicitly set at the row/file level or inherited from the table/bucket settings. Permissions are arrays of strings built with the `Permission` and `Role` helpers.\r\n\r\n```ruby\r\n# Permission and Role are included in the main require\r\nrequire 'appwrite'\r\ninclude Appwrite\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```ruby\r\ndoc = tables_db.create_row(\r\n    database_id: '[DATABASE_ID]',\r\n    table_id: '[TABLE_ID]',\r\n    row_id: ID.unique,\r\n    data: { title: 'Hello World' },\r\n    permissions: [\r\n        Permission.read(Role.user('[USER_ID]')),     # specific user can read\r\n        Permission.update(Role.user('[USER_ID]')),   # specific user can update\r\n        Permission.read(Role.team('[TEAM_ID]')),     # all team members can read\r\n        Permission.read(Role.any),                   # anyone (including guests) can read\r\n    ]\r\n)\r\n```\r\n\r\n### File Upload with Permissions\r\n\r\n```ruby\r\nfile = storage.create_file(\r\n    bucket_id: '[BUCKET_ID]',\r\n    file_id: ID.unique,\r\n    file: InputFile.from_path('/path/to/file.png'),\r\n    permissions: [\r\n        Permission.read(Role.any),\r\n        Permission.update(Role.user('[USER_ID]')),\r\n        Permission.delete(Role.user('[USER_ID]')),\r\n    ]\r\n)\r\n```\r\n\r\n> **When to set permissions:** Set row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row permissions empty.\r\n\r\n> **Common mistakes:**\r\n> - **Forgetting permissions** — the resource becomes inaccessible to all users (including the creator)\r\n> - **`Role.any` with `write`/`update`/`delete`** — allows any user, including unauthenticated guests, to modify or remove the resource\r\n> - **`Permission.read(Role.any)` on sensitive data** — makes the resource publicly readable\r\n\r\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}